Render a component within another in React - javascript

When a state is changed, React triggers componentDidUpdate() method, and by then I do:
componentDidUpdate: function () {
React.render(new SubmitButton, $('.uploader-submit').get(0));
}
As you saw, I'm rendering a SubmitButton when a specific state is changed, but my question is: is this the best behavior to get this feature done?
My scenario is: I'm uploading a photo. When the input[type=file] is changed, I create a new state property and then the componentDidUpdate() is triggered, invoking the SubmitButton.
This is my render() method:
render: function () {
return (
<div className="uploader">
<header className="uploader-header">
<div className="uploader-actions pull-left">
<div className="uploader-submit"></div>
<CancelButton router={this.props.router} />
</div>
<UploadButton callback={this.imageSelectedCallback} />
</header>
<Preview imageUri={this.state.imageUri} />
</div>
)
}
Couldn't I do something like the <Preview /> component? I mean, it is there, but something just appears when this.state.imageUri is different of null. This is the implementation of Preview:
var Preview = {
render: function () {
return (
<img src={this.props.imageUri} />
)
}
};
module.exports = React.createClass(Preview);
Yes, I know — "Preview" is invisible by default because it is an image, but I want to know if there's another approach to reach what I want: to show something based on a state, using the render method.

React doesn't render falsy values, be it a component or an attribute (like in the Preview case), e.g.
<div>{null}</div>
<img src={null} />
renders to
<div></div>
<img/>
So typically you just create a variable and conditionally assign it a component or null as was also suggested in another answer:
var button = null;
if(myConditionForShowingButton) {
button = <SubmitButton />;
}
-- or simply --
var button = myConditionForShowingButton ?
<SubmitButton /> :
null;
In cases where the component gets bigger it's typically more readable and cleaner to have a subroutine for rendering that part
var complexComponent = condition ?
this.renderComplexComponent() :
null

Yes. If-Else in JSX.
render: function () {
var submitButton;
if (this.state.imageSelected)
submitButton = <SubmitButton />;
return (
<div className="uploader">
<header className="uploader-header">
<div className="uploader-actions pull-left">
<div className="uploader-submit">{ submitButton }</div>
<CancelButton router={this.props.router} />
</div>
<UploadButton callback={this.imageSelectedCallback} />
</header>
<Preview imageUri={this.state.imageUri} />
</div>
)
}

Related

How to track changes of a referenced element with React?

I have a problem which requires me to store the texted of a referenced element in an array.
Now, I first want to display the text for each element(paragraph element with "ebookName" class) in the console, before storing it in the array.
But I have been having problems... Whenever I click an element, the console just logs the previous elements text always. I want for each paragraph element to log that specific elements text, not the previous one
Link to JS code:
import React from 'react'
import "./Styles/Ebook.css"
import { useRef } from 'react';
function Ebook() {
const bookName = useRef();
let ebookData = JSON.parse(sessionStorage.getItem("ebook"));
/*function that displays the specific text of a specific element onto the console*/
const elementLogFunction = () =>{
console.log(bookName.current)
}
return (
<section id="musicRender">
{ebookData.results.map((ebook, i)=>{
return (
<div key={i} className='ebookContentContainer'>
<div className="ebookPicture">
<img src={ebook.artworkUrl100} alt={ebook.trackName} />
</div>
<div className="ebook-description">
<p className="ebookAuthor">Author: {ebook.artistName}</p>
<p ref={bookName} className='ebookAName'>Book Name: {ebook.trackName}</p>
<p className="price">Price: R{(ebook.price * 15.36).toFixed(0)}</p>
<button onClick={elementLogFunction} className="favourites-btn">Add To Favourites</button>
</div>
</div>)
})}
</section>
)
}
export default Ebook
According to your code, ref is only referred to the same data, and the new one will override the old one. In your case, the last book data will be kept.
If you want to have individual book data separately, you can pass a param to elementLogFunction.
You also shouldn't read sessionStorage every rendering. This behavior causes a performance issue due to getting data multiple times. You can use useEffect to read data only once after the first rendering.
function Ebook() {
const [ebookData, setEbookData] = React.useState([]);
//only add data for the first rendering
useEffect(() => {
setEbookData(JSON.parse(sessionStorage.getItem("ebook")));
}, []);
/*function that displays the specific text of a specific element onto the console*/
const elementLogFunction = (ebook) =>{
console.log(ebook.trackName)
}
return (
<section id="musicRender">
{ebookData.results.map((ebook, i)=>{
return (
<div key={i} className='ebookContentContainer'>
<div className="ebookPicture">
<img src={ebook.artworkUrl100} alt={ebook.trackName} />
</div>
<div className="ebook-description">
<p className="ebookAuthor">Author: {ebook.artistName}</p>
<p ref={bookName} className='ebookAName'>Book Name: {ebook.trackName}</p>
<p className="price">Price: R{(ebook.price * 15.36).toFixed(0)}</p>
<button onClick={() => elementLogFunction(ebook)} className="favourites-btn">Add To Favourites</button>
</div>
</div>)
})}
</section>
)
}
export default Ebook

Why is an array that is passed correctly via props returning undefined?

I'm trying to load each string of an array of strings in a <li> html tag by passing this array via props:
<CardItem
src='https://static.news...koinex-banner.png'
text='my text'
label='Adventure'
path='/products'
description={["someText1", "someText2", "someText3", "someText4"]}
/>
function CardItem(props) {
return (
<>
<li className='cards__item'>
<Link className='cards__item__link' to={props.path}>
<figure className='cards__item__pic-wrap' data-category={props.label}>
<img
className='cards__item__img'
alt='Travel Image'
src={props.src}
/>
</figure>
<div className='cards__item__info'>
<h5 className='cards__item__text'>{props.text}</h5>
</div>
<CardDescription description={props.description} />
</Link>
</li>
</>
);
}
export default CardItem;
function CardDescription(props) {
return (
<div>
<ul>
<li>{props.description[0]} </li>
</ul>
</div>
)
}
export default CardDescription
And I'm getting
TypeError: Cannot read properties of undefined (reading '0')
I'm not sure why props.description prop is returning undefined.
Also, this TypeError seems to only be happening with the props.description prop.
Your code is misspelled CardDescrition to CardDescription
Try:
{props.description ? <CardDescription description={props.description} /> : ''}
and in description:
function CardDescription(props) {
return (
<div>
<ul>
{props.description.map(des => <li>des</li>)}
</ul>
</div>
)
}
please find the minimal repo I created:
https://github.com/snake-py/so-help-react-card
Explanation:
I try to explain from what I understand what is happening there.
When Carditems mounts it seems even though you hard code the values, that they are not passed on the initial render. Hence, the ternary check if the props include the description array.
I am guessing now why that is:
Perhaps because they are inside a wrapper component of Link. If you remove the Link component the code should work without the initial check.
well, that's probably because during the mounting of the three the description prop could be undefined, you could avoid this error by doing this props?.description[0], also if you want to render all the values in the array inside the CardDescrition component you could do this
props?.description.map((item) => (<li>{item}</li>))

How to inject a dinamically created element into an existing div in React JSX?

I have a list of objects photos, from a json data file, that I would like to organize into 3 different <div> columns, but I dont know how to achieve that, here is my broken non-optimized code:
<div className="container">
<div ref={leftColRef} className="left-col" />
<div ref={centreColRef} className="centre-col" />
<div ref={rightColRef} className="right-col" />
{Object.keys(photos).forEach((n, i) => {
const id = photos[n].id;
const thumb = photos[n].thumbnailUrl;
const title = photos[n].title;
const element = (
<Thumbnail id={id} title={title} thumb={thumb} />
);
if (i % 3 === 0) {
leftColRef.current.append(element);
} else if (i % 3 === 1) {
centreColRef.current.append(element);
} else {
rightColRef.current.append(element);
}
// this line works, it idsplays the data but is commented as the data needs to go inside its respective columns
// return <Thumbnail key={id} title={title} thumb={thumb} />;
})}
</div>
The idea is to insert some elements into the left-column when i%3 = 0 and others in the centre-column when i%3 = 1 and so on ...
And a link to my codesandbox
Any help/advise will be much appreciated.
Easiest is probably to prepare the data outside the render function and to render the column one by one.
You should not manipulate the DOM like it's done in jQuery using JSX
Example:
const Component = (props) => {
const filterPhotos = (column) => {
return props.photos.filter((photo,index)=> index%3==column);
}
return <>
<MyColumn photos={filterPhotos(0)}/>
<MyColumn photos={filterPhotos(1)}/>
<MyColumn photos={filterPhotos(2)}/>
</>;
}
First, using ref on div to inject stuff on it is wrong. It's the opposite of how react works.
Like charlies said, I would split the photos in 3 different arrays before the render. Then, you'll be able to do something like this :
<div ref={leftColRef} className="left-col" />
{ photosLeft.map(photo => <Thumbnail key={photo.id} {...photo} />)
</div>
when preparing your data, try to use the same object properties and component props name so you can spread it easily ( {...photo} ).
Note: Also, when rendering an array in react, each child must have a unique key props. It will help react to render on that part of dom if your data change.

Can you use jsx within template string in React render props?

Is it possible to put JSX inside a template string that is being used as a React render prop?
This is what I'm trying to do, but it leads to the link rendering as [object Object]
const Container = ({ message }) => <div className="from line 4"> {message}</div>;
const Link = () => juan;
const App = () => (
<div>
<Container message={`My message with a ${<Link />}`} />
</div>
);
One thing I tried was to put JSX instead of a template string inside message. This works, but it introduces a new div that isn't needed.
<Container
message={<div>My message {<Link />}</div>}
/>
I made this codesandbox to illustrate the problem
You can use a Fragment to render inline like you are trying to do and to prevent adding a new wrapping <div />:
const App = () => (
<div>
<Container
message={<React.Fragment>My message with a <Link /></React.Fragment>}
/>
</div>
);
Here is a forked version of your Codesandbox using React.Fragment: https://codesandbox.io/s/nrmr9l34vl

React conditionally show link if there is content on json file?

Did some searching around, but could not find anything. this is something simple on jQuery and Rails, but not sure if there is a correct way to to this in React. I have this component that is grabbing its data form a JSON file, and I want to only display a certain link if the JSON has content, otherwise I want to hide it. I tried this way so far with no luck:
renderList(projectLinks){
//let self = this;
return projectLinks.map(function(link) {
var showDemo = "";
if(link.urlDemo === ""){
console.log("i'm empty");
showDemo = "displayNone";
}
return <Panel header={link.title} eventKey={link.eventKey} key={link.title}>
<p>{link.description}</p>
<img src={link.image} className="img-thumbnail" alt="project thumbnail"/><br />
<a href={link.urlDemo} className={showDemo}>Demo </a>
<a href={link.urlCode}>Code </a>
</Panel>
});
}
so, if the json link is empty like this: "urlDemo": "", I want the link to be hidden.
Appreciate the help.
Inside render, use conditional operator. Here's an example
render(){
return (
<div>
{this.state.check ? <div>Show this if check is true</div> :
<div>Show this if check is false</div>}
<div>
)
}
Edit: A more specific example
render(){
return(
<div>
{link.urlDemo ? <a href={link.urlDemo}>Demo </a> : null}
</div>
)
}
Another way of doing the same:
render(){
return(
<div>
{link.urlDemo && <a href={link.urlDemo}>Demo </a>}
</div>
)
}

Categories