Why is my if statement always giving the same result in React? - reactjs

I have a button that changes the state of sort to either true or false. This is being passed to the App component from a child using callbacks. The first console log in the refinedApiRequest gives the value of true and then false and then true and so on as the button is click. However, the if statement keeps resulting in the else result regardless of the state of sort (true or false). What is causing this?
I want to assign a variable a value depending on the state of sort so that I can feed this value into the params of a async api get request. Thanks in advance.
class App extends React.Component {
state = { pictures: '', sort: '' };
onSortSelect = sort => {
this.setState({ sort: sort }, () => this.refinedApiRequest(sort));
}
async refinedApiRequest(sort) {
console.log(sort);
if(sort == 'true') {
console.log('its true');
} else {
console.log('its false');
}
const res = await axios.get('url', {
params: {
order: {a variable}
}
});
this.setState({ pictures: res.data });
console.log(this.state.pictures);
}

While the optional callback parameter to this.setState is guaranteed to only be executed when the component is re-rendered, in your case it still closes over the sort value of the previous render (ref: How do JavaScript closures work?).
I'd suggest following reacts advice:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
from: https://reactjs.org/docs/react-component.html#setstate
(emphasis by me)

Related

React not returning latest state until second request

I have the following method in a form component:
validateForm () {
this.setState({isValid: true});
this.formFields.forEach(field => {
let isValid = field.ref.current.validateField();
console.log('Field isValid?', isValid);
if (!isValid) {
this.setState({isValid: false});
}
console.log('Form isValid?', this.state.isValid);
});
return this.state.isValid;
}
When it is called it sets the form state isValid as true. And then loops through the child components we store in the formFields and finds out if any of them are invalid at which point it will mark the form is invalid if any return an invalid response.
Using the console logs I've found that the state for the form is always the previous state console.log('Form isValid?', this.state.isValid); until I call the method again and then it returns the correct state value...
I've seen something similar happen in ActiveRecord in Rails whereby you need to called reload on the model to make sure you're getting the latest state due to caching... Does React have something similar?
validateForm () {
let valid = true
this.formFields.forEach(field => {
let isValid = field.ref.current.validateField();
if (!isValid) {
valid = false;
}
});
if (!valid) {
this.setState({isValid: false});
}
return valid;
}
Use this code.
setState() is asynchronous. so it will not change its value right after calling setState(). it will only change its value on the next render
It is because this.setState is not synchronous. It update the state in batches.
Example:
this.setState({ isValid: true });
console.log(this.state.isValid);
This will not log true.
To do something only when the state got updated, you can pass a callback as a second parameter to this.setState.
Example:
this.setState({isValid: true}, () => {
//body
})
In your case, insted of setting state, you can set a normal variable value and then change it based on the condition instead of setting state.
validateForm () {
let valid = true;
this.formFields.forEach(field => {
let isValid = field.ref.current.validateField();
console.log('Field isValid?', isValid);
if (!isValid) {
valid = false;
}
console.log('Form isValid?', this.state.isValid);
});
return valid;
}

setState is not updating in my other method

I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )

React implementing shouldComponentUpdate on my React app with loading state

I'm having a trouble implementing shouldComponentUpdate on my React app with loadingData state. I want to prevent component to load again when nextProps has not changed from the this.props which is working fine and it messes up loadingData value some how and I can't find the reason why.
Why loadingData ended up to be true even there's no change to the data in Redux(nextProps and this.props)?
constructor(props){
super(props)
this.state = {
loadingData: false,
}
}
async componentDidMount() {
this._isMounted = true;
const { username } = this.state
this.setState({ loadingData: true })
try {
await this.props.getUserData(username)
this.setState({ loadingData: false })
} catch (err) {
console.log(err)
this.setState({ loadingData: false })
}
}
shouldComponentUpdate(nextProps, nextState) {
return this.props !== nextProps
}
render() {
return(
<div>{this.state.loadingData ? "Loading..." : this.props.userData}</div>
)
}
Updated code to show the how I set up loadingData state. Some reason, adding shouldComponentUpdate shows, Loading... on the screen instead of userData from Redux. I'm not sure why...
This will always return false because this.props is a different object than nextProps between each render.
For the simplest example of this:
console.log({} === {}) // false
By default (if using React.PureComponent) React will perform a "shallow equality" check. It will check the equality of each prop (but will not do so recursively because of performance reasons). (see the source).
As a first attempt, have you tried using React.PureComponent in place of React.Component?
If that doesn't help, I would recommend sharing the entire code snippet. shouldComponentUpdate usually is considered a "code smell" and commonly means there's a problem with another part of the code. It should not be used as control flow, but only for performance optimization.
If you still must implement shouldComponentUpdate, take a look at the default shallowEqual helper for a bit of inspiration.
Maybe you should look through this.
var jangoFett = {
occupation: "Bounty Hunter",
genetics: "superb"
};
var bobaFett = {
occupation: "Bounty Hunter",
genetics: "superb"
};
var callMeJango = jangoFett;
// Outputs: false
console.log(bobaFett === jangoFett);
// Outputs: true
console.log(callMeJango === jangoFett);

How to prevent state change in React after page refresh/view change?

My onClick-event handler adds/removes an id to a database field and changes the button color according to the toggle state true/false.
While the data update works correctly, the color state resets upon a page refresh/view change.
I guess the state needs to be passed on (the relationship is child to parent in this case) with a callback function but I am not sure.
I thought it would be necessary to 'preserve' the current state in LocalStorage but this did not solve the issue. While the LocalStorage values 'true' and 'false' remained their state (as displayed in the console), the color of the button still reset when refreshing the page.
I attached the code sections that may be of importance to assess the issue:
// initialization of toggle state
let toggleClick = false;
...
this.state = {
fav: props.favorite
};
this.toggleClass = this.toggleClass.bind(this);
}
...
componentDidUpdate(prevProps) {
if (this.props.favorite !== prevProps.favorite) {
this.setState({
fav: this.props.favorite
});
}
}
toggleClass() {
toggleClick = true;
if (!this.state.fav) {
this.addId(this.props.movie._id);
} else {
this.removeId();
}
}
...
<span
onClick={() => this.toggleClass()}
className={this.state.fav ? "favme active" : "favme"}
>★</span>
I have encountered your same problem for my React Apps. For this, I always use componentDidMount because it is called just after the render. But, if you call the setState function, it will automatically call the render, so your changes will appear. If page gets refreshed, state will be reset to default. So, the best way to achieve this is for example:
componentDidMount() {
const getDbAnswer = //fetch a signal from the db
if (getDBAnswer === true) {
this.setState({ fav: true })
else
this.setState({ fav: false })
}
You could even set a variable with localStorage. Remember, localStorage accepts only string values, no boolean.
componentDidMount() {
const getDbAnswer = //fetch a signal from the db
if (getDBAnswer === true)
localStorage.setItem("fav", "true");
else
localStorage.setItem("fav", "false");
}
And then you could just use it like this:
<div className={localStorage.getItem("fav") === "true" ? "favme active" : "favme"}>
I had a similar issue when refreshing the view. It was because the component render before the rehydration was complete. I solved my problem by using Redux Persist. We can delay the render by using Redux Persist, I put the persistStore in my main component:
componentWillMount() {
persistStore(store, {}, () => {
this.setState({rehydrated: true})
})
}
This will ensure that the component re-render when the store is rehydrated.
https://github.com/rt2zz/redux-persist

Redux Not Updating in Component Synchonously

I was under the impression that redux was synchronous but it does not seem to be in this case. I have this:
loadData() {
const filterData = {};
Object.values(this.props.filters).map(f => {
filterData[f.filter] = f.selectedValue || '';
});
this.props.fetchData({filters: filterData}); //redux call to load data
}
filterOnSubmit(filter, value, display) {
const newFilter = { filter, selectedValue: value, display };
this.props.updateFilter(newFilter);
this.loadData();
}
But the value of f does not include the selected value that is set BUT when I look at my redux devtools it is there. If I delay the mapping (or delay the loadData via putting it in a setState callback) it is there also.
action and reducer for reference:
case UPDATE_FILTER: {
const newFilter = { ...state[action.payload.filter], ...action.payload };
return { ...state, [action.payload.filter]: newFilter };
}
export function updateFilter(newFilterData) {
return {
type: UPDATE_FILTER,
payload: newFilterData
};
}
Although the redux store update is synchronous, the changes are not reflected in the same React cycle, the updates go though a re-render state, calling update lifecycle method.
After updating the store, you can call the actions that need to be called on store update in lifecycle method like componentDidUpdate (or componentWillReceiveProps which is now soon to be deprecated) or otherwise pass the filters to the loadData function directly
loadData(filters) {
const filterData = {};
Object.values(filters).map(f => {
filterData[f.filter] = f.selectedValue || '';
});
this.props.fetchData({filters: filterData}); //redux call to load data
}
filterOnSubmit(filter, value, display) {
const newFilter = { filter, selectedValue: value, display };
this.props.updateFilter(newFilter);
this.loadData(newFilter);
}
Using componentDidUpdate, you would write it like
componentDidUpdate(prevProps) {
// you have to perform an isEqual check since simply using '===' will not work for comparing arrays
if(!_.isEqual(prevProps.filters, this.props.filters)) {
this.loadData();
}
}
loadData() {
const filterData = {};
Object.values(this.props.filters).map(f => {
filterData[f.filter] = f.selectedValue || '';
});
this.props.fetchData({filters: filterData}); //redux call to load data
}
filterOnSubmit(filter, value, display) {
const newFilter = { filter, selectedValue: value, display };
this.props.updateFilter(newFilter);
}
You're on the right way, use componentDidUpdate to start a new fetch request. A thing you're possibly missing is an if-condition for changes of filter prop.
Docs remind about this:
You may call setState() immediately in componentDidUpdate() but note
that it must be wrapped in a condition like in the example above, or
you’ll cause an infinite loop.
And for your case it might be something like this, depending on the shape of filters:
componentDidUpdate(prevProps) {
if (this.props.filter !== prevProps.filter)
this.loadData();
}
Depending on code outside, you also might consider using a deep comparison inside if. But, of course, the best way would be to stay with === comparison and optimize mapStateToProps from outside (e.g. use meomization).
Do as SAmab said or move this
Object.values(this.props.filters).map(f => {
console.log(f);
filterData[f.filter] = f.selectedValue || '';
});
to your render function.

Resources