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

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

Related

React: Get data from firebase and store using setState race condition

It seems I have some sort of race condition with setState when reading data from Firebase. Upon loading the component, the listener child_added is invoked for as many as client records are in the table clients, but only the last client record is actually stored in the state using setState. I know this has to do with a lag in setState where it only works once the cycle ends, so there is a race condition with the multiple setState calls. How do I fix this so all the client records are stored correctly in this.state.clients?
class ClientsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
clients: [],
};
}
componentWillMount(){
let clientsRef = fire.database().ref('clients').orderByKey().limitToLast(100);
clientsRef.on('child_added', snapshot => {
let client = snapshot.val();
client.id = snapshot.key
this.setState({ clients: [client].concat(this.state.clients) })
})
}
}
There shouldn't be any race conditions going as setState() enqueues a change for each time it's called, in this case each client snapshot resulting from the child_added event including existing client and new client added.
The issue really might just be with your syntax for setState() which may be mutating/reverting this.state.clients each time. Instead try using spread syntax ... to append client objects to this.state.clients and update/merge this.state in an immutable way.
class ClientsTable extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
clients: []
};
}
componentWillMount(){
const clientsRef = fire.database().ref('clients').orderByKey().limitToLast(100);
clientsRef.on('child_added', snapshot => {
const client = { ...snapshot.val(), id: snapshot.key };
this.setState({
...this.state,
clients: [...this.state.clients, client]
});
});
}
}
Using a personal Firebase project I was able to get each item into the equivalent of this.state.clients and display those accordingly in both componentDidMount or componentWillMount. If you are willing, you could create a dummy Firebase project with mock data/structure matching your actual project and generate a StackBlitz if it still doesn't work.
Hopefully that helps!
The issue is that you need to clear your references to Firebase, otherwise it will cause memory leaks. In React this is typically done in componentWillUnmount.
Using your example, you need to add the following:
componentWillUnmount: function() {
this.clientsRef.off();
}
You will also need to modify your code in componentDidMount so that you use this.clientsRef, instead of using a local variable.
You can read more about this on the Firebase Blog.

Using nextProps in setState updater

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.

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.

Unable to setState to React component

I'm trying to set the state of my PlayerKey component here however the state won't update on a onClick action:
class PlayerKey extends Component {
constructor(props) {
super(props);
this.state = {
activeKeys:[]
}
}
activateKey = (e) => {
this.setState({
activeKeys:["2","3"]
})
}
render() {
return (
<div className="key" data-row-number={this.props.rowKey} data-key-number={this.props.dataKeyNumber} onClick={this.activateKey}></div>
)
}
}
I've tried console logging this.state in activateKey and it gives me the state of the component no problem (the blank array) so not sure why I can't update it?
setState method in react is asynchronous and doesn't reflect the updated state value immediately.
React may batch multiple setState() calls into a single update for performance.
So accessing the recently set state value might return the older value. To see whether the state has really been set or not, You can actually pass a function as callback in setState and see the updated state value. React Docs
As in your case, you can pass a function as callback as follows.
activateKey = (e) => {
this.setState({
activeKeys:["2","3"]
}, () => {
console.log(this.state.activeKeys); // This is guaranteed to return the updated state.
});
}
See this fiddle: JSFiddle

Event for when state is set

In React the this.setState method sets state. For instance below I set the state for name to Thomas.
this.setState({'name': 'Thomas'})
I need another object. A state property that comprises of other state properties. For instance below.
let name = 'Thomas'
this.setState({name})
console.log(this.state.data) // => undefined
let age = '26'
let data = {age}
this.setState(data)
console.log(this.state.data) {data: {age: '26'}}
this.stateChanged(state => {
let data = state.data || {}
if (state.name) data.name = state.name
this.setState({data})
})
this.setState({name})
console.log(this.state.data) // => {data: {age: '26', name: 'Thomas'}}
Is there any equivalent this.stateChanged method in React?
React gives us a couple explicit options to determine when the state's changed, or component has been updated. The third option being more of an implicit approach for your particular case:
Use the callback provided in setState(function|object nextState, [function callback]):
onClick(e) {
this.setState({
foobar: e.target.value
}, this.doAfter)
}
doAfter() {
console.log(`Just set FOOBAR to ${this.state.foobar}`)
}
Per the documentation, we can rely on this method to be invoked after the state was asynchronously set.
Use the componentDidUpdate lifecycle method:
componentDidUpdate() {
console.log(`Component updated: Foobar is ${this.state.foobar}`)
}
Per the documentation, this function will be invoked immediately after the component has rendered itself, thereby guaranteeing we have access to the latest state changes.
Compile all necessary state information in the render() method.
render() {
const compilation = {foobar: `${this.state.foobar}-bar`}
return (
<div>...</div>
)
}
Basically, just compose the object how you'd like it before returning a JSX object from render(), however the first two options above might suffice better for your needs.
Example
http://codepen.io/mikechabot/pen/KzyVVw?editors=0011

Resources