Conditional Rendering in JSX not hiding the Element - javascript

So i have this react component which contains a conditional rendering in some part. so far so good and this practice has been acting as expected all throughout my app. but surprisingly the element we're talking is not getting hidden as a result of change in condition.let me provide you with a minimal representation of relevant code because the original component is too lengthy and cumbersome.
import React from 'react';
import AnotherComponent from '../components/AnotherComponent';
export class TheComponent extends Component {
/*
1. props.data is coming from mapping component state to redux connect
2. connect file and selectors are alright, because other parts of this component
work as expected, and even same props.data is used elsewhere in the component
3. a method wihtout input as in showAnotherComponent = () => false; will hide the
AnotherComponent element successfully. but even sth like
showAnotherComponent = (data) => false; will not work!!!
4. props.data is properly injected to the render method, console.log(props.data) in reder
method will display updated value.
5. props.data is never null or undefined and so on ..
*/
showAnotherComponent = data => data.flag === 'AAA';
render() {
console.log(this.props.data); // will show the updated props.data
return (
<div className="row">
<div className="col-md-10">
<h1>Some heading</h1>
</div>
<div className="col-md-2">
{/* the element in next line will always show up invariably, whatever the
content of props.data. tried ternary, same result. */}
{this.showAnotherComponent(this.props.data) && <AnotherComponent />}
</div>
</div>
);
}
}
export default TheComponent;
Unfortunately creating a fully working sample is a bit hard, considering all the third party dependencies and the redux wiring. Nevertheless, if you have ever run into similar situation and got to the bottom of it, please share your experience with me.
note: Updated props.data is passed to the component normally. in the react dev tools it shows up and in redux dev tools history of state is quite healthy. the only problem here is that the conditional won't hide the element in a falsy state.
UPDATE. the reason for this weird rendering was a dynamic loop in the same component rendering this AnotherComponent regardless of the flag value. what made it hard to pin down was that it was rendering it in map and passing index of dynamic string content. anyhow, thank you all and sorry for the possible misleading question.

It is working fine here.
You should check the data in your props if there is any flag key or not and if there is a flag key check that if it is AAA or not.
class App extends React.Component {
constructor() {
super();
this.state = {
data: "aa"
};
}
/*
1. props.data is coming from mapping component state to redux connect
2. connect file and selectors are alright, because other parts of this component
work as expected, and even same props.data is used elsewhere in the component
3. a method wihtout input as in showAnotherComponent = () => false; will hide the
AnotherComponent element successfully. but even sth like
showAnotherComponent = (data) => false; will not work!!!
4. props.data is properly injected to the render method, console.log(props.data) in reder
method will display updated value.
5. props.data is never null or undefined and so on ..
*/
showAnotherComponent = data => data.flag === "AAA";
render() {
console.log(this.props.data); // will show the updated props.data
return (
<div className="row">
<div className="col-md-10">
<h1>Some heading</h1>
</div>
<div className="col-md-2">
{/* the element in next line will always show up invariably, whatever the
content of props.data. tried ternary, same result. */}
{this.showAnotherComponent({ flag: this.state.data }) && (
<div>asdsdasd</div>
)}
</div>
<button onClick={() => this.setState({ data: "AAA" })}>Toggle</button>
</div>
);
}
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Click on Toggle and the Data will be displayed.

Related

React component retrieves props just once, goes undefined when refreshed

I'm creating a simple movie app with moviedb. I have successfully retrieved the most popular 20 movies and put them in the app state:
constructor(props) {
super(props);
this.state = {
movieInfo: [],
}
}
componentDidMount() {
this.getMovies();
}
getMovies = async () => {
await axios.get('https://api.themoviedb.org/3/movie/popular?api_key=94d4ad026c5009bdaf4aecb8989dfa07')
.then(res => this.setState({ movieInfo: res.data.results }))
}
I know the array was retrieved correctly because when I look at the React components in Chrome dev tools I see what I want:
Screen cap of App state
Then in the render part of the App I want to pass the first element in the array to a component called Movie, which will then display some info about the movie:
return (
<div>
<Movie movie={this.state.movieInfo[0]} />
</div>
);
}
I know the movie component is getting this info correctly because I see the object representing the first movie in the Movie component props:
Movie component props
My Movie function looks like this:
return (
<div>
<h1>{props.movie.original_title}</h1>
<p>{props.movie.overview}</p>
</div>
)
}
The first time I compile it this works and I see the info I want:
Rendered App with Movie component
But incredibly, when I refresh the page I see the error message
TypeError: Cannot read properties of undefined (reading 'original_title')
How is it possible that the App will correctly pass on the info to Movie and will display it correctly once, but as soon as I refresh the page somehow it's undefined?
Thanks in advance for the help,
JD
I assume that you don't get an error when you are developing and changing code. When you save your code Hot reloading only changes parts that changed.
This means that if your app loads data and populates this.state.movieInfo with an array from BE, and your save you code, it Hot reloads and you get new data. So, this.state.movieInfo[0] is always filled with data.
When you refresh your app, it just resets to an empty array as you put it there in the constructor.
Solution is to always check if there is that first element in array before rendering the Movie component:
return (
<div>
{this.state.movieInfo[0] ? <Movie movie={this.state.movieInfo[0]} /> : null}
</div>
);
You may need to also use componentDidUpdate() with componentDidMount():
componentDidMount() {
this.getMovies();
}
componentDidUpdate() {
this.getMovies();
}
when the page reloads init state is an empty array. you have to check the array has an item to render or not.
return (
<div>
{this.state.movieInfo && this.state.movieInfo.length>0 && (<Movie
movie={this.state.movieInfo[0]} />)}
</div>
);

React hook how to avoid executing child components when parent rerenders

Given the following are all written in react hooks:
export Parent = () => {
const [msgValue, setMsgValue] = useState();
....
return {
<>
...
<Child setMsgValue={setMsgValue}/>
...
</>
}
}
shouldSkipUpdate = (oldProps, newProps) => {
...
return true;
}
export Child = React.memo({setMsgValue} => {
return (
<>
<HeavyComponent1>
<HeavyComponent2>
<InputBox onChange={setMsgValue}>
</>
)
}, shouldSkipUpdate);
My problem is that the input box is not responsive, my investigation shows that every keydown, <Child> will get executed once, even shouldSkipUpdate returns true, which in turn causes <HeavyComponent1> and <HeavyComponent2> code get executed and causes lagging.
What have I done wrong and how do I actually prevent <HeavyComponent> gets executed?
I am also a bit confused about re-render vs the component code gets executed, would be great to get clarification on this as well.
Please check below codesanbox, i have a child component which does not get rendered even if parent component gets rendered. Hope tha gives you some clarity on what needs to be done.
https://codesandbox.io/s/friendly-cerf-qme94
If the shouldSkipUpdate returns true the component doesn't rerender. Is it possible that you have HeavyComponent1 and/or HeavyComponent2 somewhere else in the tree, and those component instances get executed and rendered?
I just realised that InputBox is being wrapped by formsy which somehow updates the parent component which causes the rerender of everything.

Having Trouble Rendering an Object Passed Down in Props

Hey Guys,
this is my first post on Stack Overflow, but I will do my best to adhere to the proper formats! To sum it up, I have spent the last five hours on this react component, trying to render a list of objects being passed down from the state of my parent component, through the child's props. I have tried every which way, researched multiple solutions, but nothing is seeming to work. This is my first time using webpack, maybe there is some plugin I have to download for using React state and props? I dunno. Anyways, I will post snippets below and describe each step to the best of my ability.
My Parent App
I have had no trouble on this end, I have logged my results every step of the way so I know for a fact that the getWeather function is properly fetching data from the Weatherbit API and setting the state, which in turn as you will see, passes the array to the child via it's props.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {weatherArray: []}
this.getWeather = this.getWeather.bind(this);
}
getWeather() {
const newWeather = WeatherHelper.inputPrompt();
console.log(newWeather)
this.setState({weatherArray: newWeather})
WeatherHelper.showData()
}
Still on My Parent App
As you can see, I pass the state as a prop to the child, which would update soon as setState runs
<div id='side-apps'>
<div id='weather-side-app'>
<WeatherApp weatherArray={this.state.weatherArray} />
</div>
</div>
My Child App
So this is where it gets weird...
This is my child component, and as you can see when the component receives new props, it first checks whether or not those props are updated. Then, assuming those props are new, the componentWillReceiveProps method will set the state and re-render accordingly (or so you would think). You can also see that I have extensively used logging to try to document my problem, which I will provide a screen shot of next. Notice that in the componentWillReceiveProps method, if the props are different than the originals (i.e. updated) the method will log multiple dashes around the updated props so you can point them out (you'll see). By the way, I understand that I shouldn't even have to use the componentWillReceiveProps method, the parents setState function alone should have been enough to update my component, but alas it did not (and neither did the other 10 different ways I tried).
class WeatherApp extends React.Component {
constructor(props) {
super(props);
this.state = {currentWeather: []}
}
handleCloseClick() {
WeatherHelper.hideData();
}
componentWillReceiveProps(nextProps) {
if (nextProps.weatherArray !== this.state.currentWeather) {
console.log('---')
console.log(nextProps.weatherArray)
console.log('---')
this.setState({currentWeather: nextProps.weatherArray})
} else {
{console.log("if you see an array under this comment," +
"that means the above if statement read the state's
currentWeather array's length" +
" as zero. EVEN THOUGH the componentWillReceiveProps method" +
" updated and set the state accordingly...")}
console.log(nextProps.weatherArray)
console.log(this.state.currentWeather)
}
}
render() {
if (this.state.currentWeather.length > 0) {
return (
<div class='weatherApp-main'>
<div className='close-sidebar-button'>
<img src='https://i.postimg.cc/hj0rYKxc/close-button.png' onClick={this.handleCloseClick}/>
</div>
<div id='weather-app-header'>
<h2>{this.state.currentWeather}</h2>
<h3></h3>
<h4></h4>
</div>
<div id='weather-app-table'>
</div>
</div>
)
} else {
{console.log(this.state.currentWeather)}
return <p></p>
}
}
}
Child Component Logging Details
if you look the the screen shot, you can see that my logging from my child's componentWillReceiveProps method proves that the setState function must have been called, but for some reason it does not update the child's html and display data.
child component logging details
Child Component React Details
Okay last thing... I also am posting the react chrome dev tool that shows you what components have props and states. You can see in this image, that the child component did receive the intended props and updated it's state accordingly. But again, for some reason it will not allow me to access them and display results
child component react details
I really apologize if this question is too lengthy... And yes, I understand that there are multiple similar posts already solved, but I have looked at most of them, and my situation just does not apply from what I can tell... I am not an expert, but I am no newbie to react. This has really stumped me. Thank you so much in advance for your help. I can provide additional screenshots, code snippets, or even videos if that helps!!!
makezi
EDIT
I added replace the block of code with a map function, and as you can see, the problem is not how I am delivering the data to the DOM. The problem is for some reason, my component is not reading the data in it's state (I will post a more detailed screenshot to better document the problem)
render() {
console.log('++++')
console.log(this.state.currentWeather.length);
if (this.state.currentWeather.length > 0) {
return (
<div class='weatherApp-main'>
<div className='close-sidebar-button'>
<img src='https://i.postimg.cc/hj0rYKxc/close-button.png' onClick={this.handleCloseClick}/>
</div>
<div id='weather-app-header'>
{this.state.currentWeather.map(item => <div><span>{item.city}</span></div>)}
<h3></h3>
<h4></h4>
</div>
<div id='weather-app-table'>
</div>
</div>
)
} else {
{console.log("if you see an array under this comment," +
"that means the above if statement read the state's currentWeather array's length" +
" as zero. EVEN THOUGH the componentWillReceiveProps method" +
" updated and set the state accordingly...")}
{console.log(this.state.currentWeather)}
return <p></p>
}
}
better logging details
Second Edit for Minh Hưng Trần's Comment
I updated my parent's getWeather function to be asynchronous, also edited logging to be more descriptive. You can see even though the currentWeather array in state has 8 objects, the length is still being read as zero! I don't think the problem is comparing the arrays. For some reason, my render function cannot read the data in my state... or it is not being updated when setState is ran. **you cannot tell from the screenshot, but the nextProps array is read and fully logged to console. although after running setState, the ''console.log(this.state.currentWeather)'' logged an empty array, weird right? **
componentWillReceiveProps(nextProps) {
if (nextProps.weatherArray !== this.state.currentWeather) {
console.log("--nextProps weatherArray--")
console.log(nextProps.weatherArray)
console.log('--------------------------')
this.setState({currentWeather: nextProps.weatherArray})
console.log("==child's state==")
console.log(this.state.currentWeather)
console.log("=================")
} else {
console.log(nextProps.weatherArray)
console.log(this.state.currentWeather)
}
}
render() {
console.log("++child's state's length in render method++")
console.log(this.state.currentWeather.length);
console.log("+++++++++++++++++++++++++++++++++++++++++++")
if (this.state.currentWeather.length > 0) {
return (
<div class='weatherApp-main'>
<div className='close-sidebar-button'>
<img src='https://i.postimg.cc/hj0rYKxc/close-button.png' onClick={this.handleCloseClick}/>
</div>
<div id='weather-app-header'>
{this.state.currentWeather.map(item => <div><span>{item.city}</span></div>)}
<h3></h3>
<h4></h4>
</div>
<div id='weather-app-table'>
</div>
</div>
)
} else {
{console.log("if you see an array under this comment," +
"that means the above if statement read the state's currentWeather array's length" +
" as zero. EVEN THOUGH the componentWillReceiveProps method" +
" updated and set the state accordingly...")}
{console.log(this.state.currentWeather)}
return <p></p>
}
}
better logging details
To reiterate, in my componentwillreceiveprops function: the "console.log('nextProps.weatherArray)" DOES emit the full array of data... HOWEVER, the "console.log('this.state.currentWeather)" does NOT. Even after setState is run. But then when I use the React Chrome Dev Tools, you can see that the state DID in face get passed to the child component. My render method just cannot read the data for some reason..
THIRD EDIT
This first snippet is my parent component. You can see that it is passing the weatherArray to the child's props. This getWeather function DOES work by the way, I have been validating it through logging. The problem is on my child's end,I am sure of it.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {weatherArray: []}
this.getWeather = this.getWeather.bind(this);
}
async getWeather() {
const newWeather = await WeatherHelper.inputPrompt();
console.log(newWeather)
this.setState({weatherArray: newWeather})
WeatherHelper.showData()
}
render() {
return (
<div>
<div id='main'>
<div id='introduction'>
<Intro />
</div>
<div id='attachment'>
<Attachment getWeather={this.getWeather} />
</div>
<div id='body'>
<div id='about'>
<About />
</div>
<div id='personal-info-skills'>
<div id='info'>
<p id='info-header'>personal information</p>
<Info />
</div>
<div id='pskills'>
<p id='pskills-header'>personal skills</p>
<PSkills />
</div>
</div>
<div className='divider'>
<p></p>
</div>
<div id='professions-languages'>
<div id='professions'>
<p id='professions-header'>professions</p>
<Professions />
</div>
<div id='languages'>
<p id='languages-header'>languages</p>
<Languages />
</div>
</div>
</div>
</div>
<div id='side-apps'>
<div id='weather-side-app'>
<WeatherApp weatherArray={this.state.weatherArray} />
</div>
</div>
</div>
)
}
}
Now I will post my child's code snippet after commenting out componentWillReceiveProps. Instead of trying to display data in my child's state, now I am simply trying to use props data that was passed from the child's parent component. Unfortunately, I am still running into the same problem. My child's render function is reading the props array as empty, and therefore cannot display data. I can't take away that if statement at the beginning of the render method because then my code throws errors when it tries to read attributes of an empty array
render() {
if (this.props.weatherArray.length > 0) {
return (
<div class='weatherApp-main'>
<div className='close-sidebar-button'>
<img src='https://i.postimg.cc/hj0rYKxc/close-button.png' onClick={this.handleCloseClick}/>
</div>
<div id='weather-app-header'>
{this.props.weatherArray.map(item => <div><span>{item.city}</span></div>)}
<h3></h3>
<h4></h4>
</div>
<div id='weather-app-table'>
</div>
</div>
)
} else {
{console.log("if you see an array under this comment," +
"that means the above if statement read the state's currentWeather array's length" +
" as zero. EVEN THOUGH the React Chrome Dev Tools shows that" +
" the child has a full currentWeather array in props...")}
{console.log(this.state.currentWeather)}
return <p></p>
}
}
}
before running getWeather function screen shot
after running getWeather function screen shot
Look at the two above screenshots. The first one is before I run my fetch function, so the React Chrome Dev Tools show my child's props array is empty (of course). BUT the after screen shot shows that even though the React Dev Tools show my child component's props has a full array, my render method still reads that prop array's length as 0....
React documentation suggests avoiding legacy lifecycles such as componentWillReceiveProps.
A common misconception is that getDerivedStateFromProps and componentWillReceiveProps are only called when props “change”. These lifecycles are called any time a parent component rerenders, regardless of whether the props are “different” from before. Because of this, it has always been unsafe to unconditionally override state using either of these lifecycles. Doing so will cause state updates to be lost.
Documentation says:
componentDidMount() is invoked immediately after a component is mounted... If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
EDIT:
Looking at the third edit, I've been assuming that you were fetching calls through the componentDidMount lifecycle, since you kept iterating that the fetching 'works'.
Since the snippets above do not actually show a componentDidMount method, I suggest trying to move all fetching logic to the appropriate componentDidMount method then setting state from there.
First, the getWeather function is fetching Weatherbit API, so you must use await
on fetch function. In your code above, I think you use function WeatherHelper.inputPrompt() to fetch data, so the function will be:
async getWeather() {
const newWeather = await WeatherHelper.inputPrompt();
console.log(newWeather)
this.setState({weatherArray: newWeather})
WeatherHelper.showData()
}
In your code above, console.log was print data because it can print data asynchronous (according to my experience).
Second, you shouldn't compare array with array by !==, read this.
Please change your code, if not working, please log data of nextProps and state of Child component in componentWillReceiveProps and show me the screenshot of it.
Sorry my english very bad.
You don’t need to use componentWillReceiveProps. This is adding a lot of unnecessary complexity for you. When the state changes in the parent component, it automatically re-renders its children. Omit the componentWillReceiveProps and render in the child component using this.props.weatherArray.

Clearing specific redux state before rendering component

I have the following "Buy" button for a shopping cart.
I also have a component called Tooltip, which will display itself for error/success messages. It uses the button's width to determine it's centre point. Hence, I use a `ref since I need to access it's physical size within the DOM. I've read that it's bad news to use a ref attribute, but I'm not sure how else to go about doing the positioning of a child component that is based off the physical DOM. But that's another question... ;)
I am persisting the app's state in localStorage. As seen here:
https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
The issue I'm running into is that I have to clear the state's success property before rendering. Otherwise, if I have a success message in the state, on the initial render() the Tooltip will attempt to render as well. This won't be possible since the button it relies on is not yet in the DOM.
I thought that clearing the success state via Redux action in componentWillMount would clear up the success state and therefore clear up the issue, but it appears that the render() method doesn't recognize that the state has been changed and will still show the old value in console.log().
My work-around is to check if the button exists as well as the success message: showSuccessTooltip && this.addBtn
Why does render() not recognize the componentWillMount() state change?
Here is the ProductBuyBtn.js class:
import React, { Component } from 'react';
import { connect } from 'react-redux'
// Components
import Tooltip from './../utils/Tooltip'
// CSS
import './../../css/button.css'
// State
import { addToCart, clearSuccess } from './../../store/actions/cart'
class ProductBuyBtn extends Component {
componentWillMount(){
this.props.clearSuccess()
}
addToCart(){
this.props.addToCart(process.env.REACT_APP_SITE_KEY, this.props.product.id, this.props.quantity)
}
render() {
let showErrorTooltip = this.props.error !== undefined
let showSuccessTooltip = this.props.success !== undefined
console.log(this.props.success)
return (
<div className="btn_container">
<button className="btn buy_btn" ref={(addBtn) => this.addBtn = addBtn } onClick={() => this.addToCart()}>Add</button>
{showErrorTooltip && this.addBtn &&
<Tooltip parent={this.addBtn} type={'dialog--error'} messageObjects={this.props.error} />
}
{showSuccessTooltip && this.addBtn &&
<Tooltip parent={this.addBtn} type={'dialog--success'} messageObjects={{ success: this.props.success }} />
}
</div>
);
}
}
function mapStateToProps(state){
return {
inProcess: state.cart.inProcess,
error: state.cart.error,
success: state.cart.success
}
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (siteKey, product_id, quantity) => dispatch(addToCart(siteKey, product_id, quantity)),
clearSuccess: () => dispatch(clearSuccess())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductBuyBtn)
Well, it seems to be a known problem that's easy to get into (harder to get out of, especially in a nice / non-hacky way. See this super-long thread).
The problem is that dispatching an action in componentWillMount that (eventually) changes the props going in to a component does not guarantee that the action has taken place before the first render.
So basically the render() doesn't wait for your dispatched action to take effect, it renders once (with the old props), then the action takes effect and changes the props and then the component re-renders with the new props.
So you either have to do what you already do, or use the components internal state to keep track of whether it's the first render or not, something like this comment. There are more suggestions outlined, but I can't list them all.

Conditionally rendering a react component

For a component that is hidden at some point in its lifecycle, what is the best way to render it?
1) render the component, but do not show it (display:none).
2) render the component only when needed.
What is better for performance? If the component's props and state update later, would it be better to have the component present, but hidden in the virtual DOM?
render() {
return (
<div style={{display: this.props.visible ? 'block' : 'none'}}>
<RestofComponentHere />
</div>
);
}
or this:
render() {
if (!this.props.visible) {
return null;
}
return (
<div>
<RestofComponentHere />
</div>
);
}
Just go with what works best for that situation. Sometimes it's method 1 and sometimes method 2. If you find out that it's slowing down your app then it's pretty easy to convert to method 1, but this should only if happen if you are conditionally rendering a ton of times. When you have a ref to the component then you want to always render it so that you can access the ref in componentDidMount and so you would have it hidden.
First method is faster as shown in answer here but don't do micro optimizations for the sake of it if conditionally rendering is cleaner code.
I'v used a mixture in my apps.
I would suggest to go with the state values and have a condition based on state values to decide whether the component to be shown or hidden.
constructor(props){
//Set some initial condition for display depending on prop or default value
//Something similar to this:
display: props.toDisplay === undefined ? true : props.toDisplay
}
componentDidMount(){
//update the state depending on the response or change
}
onClick(event){
//It might depend on someOther component's click action.
}
The render method would just have the following:
render(){
const toDisplay = this.state.display
if(toDisplay &&
// Component To render
)
}

Categories

Resources