Using nextProps in setState updater - reactjs

Is there a possibility to use the nextProps arg in a setState called in componentWillReceiveProps ?
From the React documentation the signature is defined as follows:
setState((prevState, props) => stateChange[, callback])
so that I don't see how it is possible to use nextProps other than using the shallow merge:
setState(stateChange[, callback])
for instance:
componentWillReceiveProps(nextProps) {
this.setState({
data: nextProps.data
}, myCallBack)
}
However this latter is not well suited for multiple setState so that I don't want/I can't use it.

This should work like you are expecting. Although, since you are reading from props and not state, multiple setState calls shouldn't be an issue.
componentWillReceiveProps(nextProps) {
this.setState( prevState => {
return { data: nextProps.data }
}, myCallBack)
}

You can update your state in multiple ways:
this.setState(() => {
return {
data: nextProps.data
}
});
or
this.setState({
data: nextProps.data
});
It will be the same since you're not updating based on your state, but on the nextProps. You can also do multiple setState calls and if they are in the same render cycle, React can and will batch these updates for you.

Related

Why I can't set the state from inside componentDidUpdate even with a condition?

I'm comparing props from within componentDidUpdate and trying to update the state and, after that, fetch some data (which depends of some state params).
componentDidUpdate(prevProps) {
if (prevProps.location.search.split("?description=")[1] !==
this.props.location.search.split("?description=")[1]) {
this.setState({
searchParams: this.getInitialSearchParams(),
isLoaded: false,
entities: []
})
this.fetchMore()
}
}
But, as the question suggests, when I'm going to fetch the data, the function is using a previous state.
Sandbox: https://codesandbox.io/s/0y8cm
To evaluate the error do the following: use the navbar to search one of the entities. Check the console to see the state. Do another search in the navbar to the same entity. Check the console again and see the unchanged state.
What am I doing wrong?
Thanks a lot!
This is because setState is asynchronous and the value is not set immediately after the call. You should use its callback to follow up.
setState(updater, [callback]);
This is useful for when you want to use the state values right after updating them. So instead you would have:
componentDidUpdate(prevProps) {
if (prevProps.location.search.split("?description=")[1] !==
this.props.location.search.split("?description=")[1]) {
this.setState({
searchParams: this.getInitialSearchParams(),
isLoaded: false,
entities: []
}, this.fetchMore); //or () => this.fetchMore() if you want to send params
}
}
You can read more about setState here: https://reactjs.org/docs/react-component.html#setstate
It's very useful to read about the setState and React's lifecycle as well.
Hope this was helpful.

React: why we have callback in setState?

Why do we have a callback in setState?
I am new to react and I am trying to understand but I am not able to understand fully.
increasePrice = () => {
this.setState({
price: this.state.price + 1
})
this.props.getPriceData(this.state.price)
}
setstate is async process;its not updated
example if you want to pass the data after modified you need to use callback as below
increasePrice = () => {
this.setState({
price: this.state.price + 1
},()=>{
this.props.getPriceData(this.state.price)
})}
As mentioned in the doc:
https://reactjs.org/docs/react-component.html#setstate
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
You should never modify the state object directly, but instead use the updater function provided by setState():
// Bad
this.state.counter = this.state.counter + this.props.step;
// Good
this.setState((state, props) => {
return {
counter: state.counter + props.step
};
});
setState is Asynchronous in nature. If you want to use the updated state just after setting it Callback of state is used.
Example-
setState(
{ name: "ABC" },
() => console.log(this.state)
);
// => { name: "ABC" }
Read this SO post to know when to use it.

React Redux | setState using mapStateToProps

I want to set the local state of a component using props obtained from mapStateToProps. When I use this.setState in ComponentWillMount() method or ComponentDidMount() method, it is undefined.
componentWillMount() {
this.props.fetchBooks(this.props.id);
this.setState({
books:this.props.books
})
}
const mapStateToProps = (state, ownProps) => {
let id = ownProps.match.params.book_id;
return {
id: id,
books: state.library.books,
};
};
The 'books' data is retrieved using the fetchBooks() function obtained by mapDispatchToProps.
How can I set the sate using the props obtained from mapStateToProps?
The problem might be that this.props.fetchBooks is an asynchronous operation. In that case, this.setState is called before the operation is completed, hence the undefined value.
But why do you want to store the books in the local state? Why don't you use directly the books prop instead?
Using derived state is usually unnecessary, and might be a bad idea (see this blog post from the official React website for an in-depth explanation).
If you really need to store the books in the local state, you could:
if fetchBooks returns a promise, use then or await (and move the code in componentDidUpdate as suggested by #Baruch)
use getDerivedStateFromProps
Your componentWillMount lifecycle method will only fire once, so using it to call the function that fetches the data and also use the data will most likely not work. You should use componentWillReceiveProps instead.
constructor(props) {
props.fetchBooks(props.id);
}
componentWillReceiveProps(nextProps) {
if (nextProps.books != this.props.books) {
this.setState({books: nextProps.books});
}
}
You better use straight inside your render() {} this.props.assignedSupervisors instead of putting it in a class state.
Otherwise use
componentDidUpdate(prevProps, prevState) {
if (this.props.assignedSupervisors && this.props.assignedSupervisors !== prevProps.assignedSupervisors) {
this.setState({
supervisors:this.props.assignedSupervisors
})
}
}

console not showing updated values after setState [duplicate]

Ok, i'll try and make this quick because it SHOULD be an easy fix...
I've read a bunch of similar questions, and the answer seems to be quite obvious. Nothing I would ever have to look up in the first place! But... I am having an error that I cannot fathom how to fix or why its happening.
As follows:
class NightlifeTypes extends Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: event.target.checked});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
More code surrounds this but this is where my problem lies. Should work, right?
I've also tried this:
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: !this.state.barClubLounge});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
So I have those two console.log()'s, both should be the same. I'm literally setting the state to be the same as the event.target.checked in the line above it!
But it always returns the opposite of what it should.
Same goes for when I use !this.state.barClubLounge; If it starts false, on my first click it remains false, even though whether the checkbox is checked or not is based off of the state!!
It's a crazy paradox and I have no idea whats going on, please help!
Reason is setState is asynchronous, you can't expect the updated state value just after the setState, if you want to check the value use a callback method. Pass a method as callback that will be get executed after the setState complete its task.
Why setState is asynchronous ?
This is because setState alters the state and causes re rendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better UI experience and performance.
From Doc:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Using callback method with setState:
To check the updated state value just after the setState, use a callback method like this:
setState({ key: value }, () => {
console.log('updated state value', this.state.key)
})
Check this:
class NightlifeTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
}
handleOnChange = (event) => { // Arrow function binds `this`
let value = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: value}, () => { //here
console.log(value);
console.log(this.state.barClubLounge);
//both will print same value
});
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
}
ReactDOM.render(<NightlifeTypes/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
Since setState is a async function. That means after calling setState state variable does not immediately change. So if you want to perform other actions immediately after changing the state you should use callback method of setstate inside your setState update function.
handleOnChange = (event) => {
let inputState = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: inputState}, () => { //here
console.log(this.state.barClubLounge);
//here you can call other functions which use this state
variable //
});
}
}
This is by-design due to performance considerations. setState in React is a function guaranteed to re-render Component, which is a costly CPU process. As such, its designers wanted to optimize by gathering multiple rendering actions into one, hence setState is asynchronous.

Why does the state in ReactjS vs firebase data not match?

I am learning to use Firebase using reactJS. I am trying to update my firebaseList state to match the Firebase database.
...
const dbRef = firebase.initializeApp(config).database().ref().child('text');
class App extends Component {
constructor(){
super();
this.state = {
text: "",
firebaseList: {}
}
}
componentDidMount(){
dbRef.on('value', snap => {
console.log(snap.val());
this.setState({
firebaseList: snap.val()
});
console.log('firebaseList: ', this.state.firebaseList);
});
}
...
When I go to chrome console after pushing a new string, "This is a test!", this is displayed:
Object {-KeoiS8luCsuKhzc_Eut: "asdf", -Keol-2Si05dmkmuac8l: "This is a test!"}
firebaseList: Object {-KeoiS8luCsuKhzc_Eut: "asdf"}
Why is my firebaseList state behind by one element? Why does snap.val() have two key-value pairs and firebaseList only has one key-value pairs?
this.setState is not guaranteed to be synchronous, because they can be processed in batches. This means that although you call console.log in your code after your setState, the state may not have actually changed yet.
From the React docs:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
If you would like to check if your state is updated, you can either used a callback as the second argument to setState or put some logic in the shouldComponentUpdate(nextProps, nextState) lifecycle method.
Example:
componentDidMount(){
dbRef.on('value', snap => {
console.log(snap.val());
this.setState({
firebaseList: snap.val()
}, () => console.log('firebaseList: ', this.state.firebaseList))
});
}
or
shouldComponentUpdate(nextProps, nextState) {
if (this.state.firebaseList !== nextState.firebaseList) {
console.log('firebaseList: ', nextState.firebaseList);
}
}
setState Documentation: (Note the function signature, setState(nextState, callback))
https://facebook.github.io/react/docs/react-component.html#setstate
shouldComponentUpdate Documentation:
https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate

Resources