Event for when state is set - reactjs

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

Related

Is automatic update of React's context usual behavior?

I thought that context acts in a similar way as state so I've created function handleUpdate that can update state which is context using as a value. Afterwards I've noticed that context is being updated without triggering handleUpdate.
Provider:
<DashboardContext.Provider value={{dashboard:this.state,handleChange:this.handleChange}}>
{/*...*/}
</DashboardContext.Provider>
handleChange function
handleChange=(what, value)=> this.setState({[what]:value});
In another component which uses context: this triggers updating of context without calling handleUpdate.
let updatedTasks = this.context.dashboard.tasks;
updatedTasks[task.id] = {/* ... something */};
Afterwards it changes context value and parents state (which is context using) without calling setState. Is this usual behavior? I though that all states should be handled with setState function.
As the actual workaround to lose reference on contexts object I've used:
let updatedTasks = JSON.parse(JSON.stringify(this.context.dashboard.tasks));
but it doesn't seems like a correct solution for me.
Edit: as #Nicholas Tower suggested solution:
my current code
State in constructor now looks like this:
this.state = {
value: {
dashboard: {
// everything from state is now here
},
handleChange: this.handleChange,
}
};
I pass state.value instead of custom object now
<DashboardContext.Provider value={this.state.value}>
{/*...*/}
</DashboardContext.Provider>
but still when I do this, context and state (both) are being updated without calling handleChange
let updatedTasks = this.context.dashboard.tasks;
updatedTasks[task.id] = {/* ... something */};
The issue you have is in this part:
value={{dashboard:this.state,handleChange:this.handleChange}}
Every time your component renders (for whatever reason), this line will create a new object. The dashboard property and handleChange property may be unchanged, but the object surrounding them is always new. That's enough that every time it renders, the value changes, and so all descendants that use the value need to be rerendered too.
You'll need to modify your code so that this object reference does not change unless you want it to. This is typically done by putting the object into the component's state.
class Example {
handleChange = (what, value) => {
this.setState(oldState => ({
value: {
... oldState.value,
[what]:value
}
});
}
state = {
value: {
dashboard: {
// whatever you used to put in state now goes here
},
handleChange: this.handleChange
}
}
render() {
return (
<DashboardContext.Provider value={this.state.value}>
{/*...*/}
</DashboardContext.Provider>
)
}
}
You can see mention of this in React's documentation on context: https://reactjs.org/docs/context.html#caveats

keys in object are undefined but object is not (react-redux)

So I have a strange issue with the data I'm fetching from an API. In my reducer I've set the initial state to an empty object and in my component's render method I'm accessing this fetched data using this.props.data.value.one.
The actual fetched data from the API look like this:
{data:
{value: {one: '1'}}
}
However when I try to console.log(this.props.data) it will log {value: {one: '1'}} after logging undefined a couple of times but will log this.props.data.value.one as undefined.
I'm doing similar stuff in my other reducers which are getting data from other API's and they work fine.
App.js Component:
#connect(store => store)
...
componentWillMount = () => {//API Call here}
render = () => {
return (
<div><p>{this.props.data.value.one}</p><div>
)
}
reducer.js:
...
export default function reducer(state={data: {}}, action) {
...
case 'FETCHED_DATA':
return {...state, data: action.data}
action.js:
...
fetchData = user => dispatch => {
//request.get...
.then(res => dispatch({type: 'FETCHED_DATA', data: res.body.data})
}
Since you set you initial state to be an empty object, and the data you get is though an API call which is async, so when the component renders first time this.props.data is undefined.
What you need to do is check if the data is available before using it
Add a conditional check like
this.props.data && this.props.data.value.one
Since you have defined initial state as data: {}, and not as data : {value : ''}, and thus when you fetched data from the api data object is not initialized as data.value to access it. Setting it to the latter should work.
There are 2 issues i see here:
As mentioned in other answers and comments, you should always
conditionally access objects that are fetched asynchronously.
this.props.myObj && this.props.myObj.myValue
By the way, don't fetch inside componentWillMount, instead do it
in componentDidMount. You can read about the reasons for this in
the DOCS.
Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.
By the look of the shape of your reducer (state) it seems that the
data object is nested inside the reducer:
state={data: {}}
So you can't access it directly from props (unless you are changing
the shape in mapStateToProps).
So i would expect to see this:
this.props.reducerName.data.value.one
instead of this:
this.props.data.value.one

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

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

When to use 'componentDidUpdate' method?

I wrote dozens of Reactjs files, but I never used the componentDidUpdate method.
Is there any typical example of when need to use this method?
I want a real-world example, not a simple demo.
A simple example would be an app that collects input data from the user and then uses Ajax to upload said data to a database. Here's a simplified example (haven't run it - may have syntax errors):
export default class Task extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
name: "",
age: "",
country: ""
};
}
componentDidUpdate() {
this._commitAutoSave();
}
_changeName = (e) => {
this.setState({name: e.target.value});
}
_changeAge = (e) => {
this.setState({age: e.target.value});
}
_changeCountry = (e) => {
this.setState({country: e.target.value});
}
_commitAutoSave = () => {
Ajax.postJSON('/someAPI/json/autosave', {
name: this.state.name,
age: this.state.age,
country: this.state.country
});
}
render() {
let {name, age, country} = this.state;
return (
<form>
<input type="text" value={name} onChange={this._changeName} />
<input type="text" value={age} onChange={this._changeAge} />
<input type="text" value={country} onChange={this._changeCountry} />
</form>
);
}
}
So whenever the component has a state change it will autosave the data. There are other ways to implement it too. The componentDidUpdate is particularly useful when an operation needs to happen after the DOM is updated and the update queue is emptied. It's probably most useful on complex renders and state or DOM changes or when you need something to be the absolutely last thing to be executed.
The example above is rather simple though, but probably proves the point. An improvement could be to limit the amount of times the autosave can execute (e.g max every 10 seconds) because right now it will run on every key-stroke.
I made a demo on this fiddle as well to demonstrate.
For more info, refer to the official docs:
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).
Sometimes you might add a state value from props in constructor or componentDidMount, you might need to call setState when the props changed but the component has already mounted so componentDidMount will not execute and neither will constructor; in this particular case, you can use componentDidUpdate since the props have changed, you can call setState in componentDidUpdate with new props.
This lifecycle method is invoked as soon as the updating happens. The most common use case for the componentDidUpdate() method is updating the DOM in response to prop or state changes.
You can call setState() in this lifecycle, but keep in mind that you will need to wrap it in a condition to check for state or prop changes from previous state. Incorrect usage of setState() can lead to an infinite loop.
Take a look at the example below that shows a typical usage example of this lifecycle method.
componentDidUpdate(prevProps) {
//Typical usage, don't forget to compare the props
if (this.props.userName !== prevProps.userName) {
this.fetchData(this.props.userName);
}
}
Notice in the above example that we are comparing the current props to the previous props. This is to check if there has been a change in props from what it currently is. In this case, there won’t be a need to make the API call if the props did not change.
For more info, refer to the official docs:
componentDidUpdate(prevProps){
if (this.state.authToken==null&&prevProps.authToken==null) {
AccountKit.getCurrentAccessToken()
.then(token => {
if (token) {
AccountKit.getCurrentAccount().then(account => {
this.setState({
authToken: token,
loggedAccount: account
});
});
} else {
console.log("No user account logged");
}
})
.catch(e => console.log("Failed to get current access token", e));
}
}
I have used componentDidUpdate() in highchart.
Here is a simple example of this component.
import React, { PropTypes, Component } from 'react';
window.Highcharts = require('highcharts');
export default class Chartline extends React.Component {
constructor(props) {
super(props);
this.state = {
chart: ''
};
}
public componentDidUpdate() {
// console.log(this.props.candidate, 'this.props.candidate')
if (this.props.category) {
const category = this.props.category ? this.props.category : {};
console.log('category', category);
window.Highcharts.chart('jobcontainer_' + category._id, {
title: {
text: ''
},
plotOptions: {
series: {
cursor: 'pointer'
}
},
chart: {
defaultSeriesType: 'spline'
},
xAxis: {
// categories: candidate.dateArr,
categories: ['Day1', 'Day2', 'Day3', 'Day4', 'Day5', 'Day6', 'Day7'],
showEmpty: true
},
labels: {
style: {
color: 'white',
fontSize: '25px',
fontFamily: 'SF UI Text'
}
},
series: [
{
name: 'Low',
color: '#9B260A',
data: category.lowcount
},
{
name: 'High',
color: '#0E5AAB',
data: category.highcount
},
{
name: 'Average',
color: '#12B499',
data: category.averagecount
}
]
});
}
}
public render() {
const category = this.props.category ? this.props.category : {};
console.log('render category', category);
return <div id={'jobcontainer_' + category._id} style={{ maxWidth: '400px', height: '180px' }} />;
}
}
When something in the state has changed and you need to call a side effect (like a request to api - get, put, post, delete). So you need to call componentDidUpdate() because componentDidMount() is already called.
After calling side effect in componentDidUpdate(), you can set the state to new value based on the response data in the then((response) => this.setState({newValue: "here"})).
Please make sure that you need to check prevProps or prevState to avoid infinite loop because when setting state to a new value, the componentDidUpdate() will call again.
There are 2 places to call a side effect for best practice - componentDidMount() and componentDidUpdate()
#K.tin, if you call setState in componentDidUpdate(), would that cause the same data to be fetched again?
For example, [id, data_for_id] are states, and id can be changed by a click counter and data_for_id is fetched from a web API.
Now we click to change Id by setState(), and componentDidUpdate() is executed, which fetches the data_for_id, we do setState for data_for_id, which will trigger another componentDidUpdate().
The first time componentDidUpdate is called, we have prevState.ID = 0 and state.ID=1, so componentDidUpdate is run. The second time we have prevState.ID = 1 and state.ID = 1, and componentDidUpdate can be avoid entirely, which perhaps could also be implemented with shouldComponentUpdate().
Still, this causes TWO rerenders, one for ID change and one for data_for_id change, Ideally, once we detect ID change, data_for_id should be fetched, and we should have [Id, data_for_id] state changed in a single shot, and the rerender happens only once for this change ID click.
So as a general rule, we should not do any setState in componentDidUpdate(), if the change of two or more state components are related, we should perform the changes together in one place and setState in a single shot.
This is just my reasoning. I am not a react guru, so please comment.
You should be careful when it's used. because it's making multiple API calls. Sometimes unlimited calls
componentDidUpdate should not have setState inside of it. you should use componentDidUpdate specially in manipulating the dom base on this.state values. CRUD or updating state values is not good inside of componentDidUpdate. Also dont fetching new data inside of componentDidUpdate as is can cause multiple http request on server and sometimes can cause laggy specially when the code is not structural or multiple use of setState.

Resources