What is the correct place to load data in react?
According to the docs, the componentDidMount
is the recommended place but it is run after rendering which appears to be a silly place to load data. Indeed my experiment below shows the data/state is never rendered. Only if data is loaded in componentWillMount
do I see the results.
Example: In the following https://jsfiddle.net/sf9n7zj1/3/ the list of toDos
is initially one item ['One']
and should subsequently load 2 more items to become ['One', 'Two', 'Three']
. This happens correctly as is shown in the state but is never rendered and the UI only shows One
.
Clicking on the "ToDos" <h2>
tag in the fiddle will log the component state and there we see (in the developer console) that the 3 toDos and foo=1 are correctly set in state. Why is this never rendered?
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
foo: 0,
toDos: ['One']
}
this.updateFoo = () => {
console.log("updateFoo", this.state)
this.setState((state) => {
state.foo = 1;
})
}
}
// componentWillMount() {
componentDidMount() {
this.setState((state) => {
state.toDos = [...state.toDos, 'Two', 'Three']
})
console.log("Loaded ToDos...", this.state);
}
render() {
return (
<div>
<h2 onClick={this.updateFoo}>ToDos {this.state.foo}</h2>
<ul>
{this.state.toDos.map(item => <li key={item}>{item}</li>)}
</ul>
</div>
)
}
}
const container = document.getElementById('app');
const root = ReactDOM.createRoot(container);
root.render(<TodoApp />);
Note:
- Moving the data loading to the (deprecated)
componentWillMount
causes everything to work but the react blog themselves recommend loading data incomponentDidMount
- Clicking sets state.foo to
1
but this too, is not rendered
2
Answers
You’re not replacing state, you’re mutating state:
So React doesn’t see that an update has been made. In the state setting operation, don’t change properties on the state object. Return a new object to reflect the updated state. For example:
And:
Note the additional parentheses around the curly braces, indicating that this function is a single expression returning an object rather than a block of code.
Your problem is a symptom of a different issue – state mutation.
In both
setState
calls in the sample code you are mutating statestate.toDos =
. This will cause missed re-renders, which is why it appears thatcomponentWillMount
is working correctly butcomponentDidMount
isn’t.Since
componentWillMount
runs before the first render, the state is "updated" (mutated) before it’s used the first time. WithcomponentDidMount
the state is updated after the render. This would normally just trigger a re-render and you’d see your correct value near instantly. But the mutation causes that re-render to be skipped.You can correct your state updates to be non-mutating with something like:
Regarding but it is run after rendering which appears to be a silly place to load data, it may seem that way, but in reality the docs are correct. An additional render isn’t a problem, and most real world data loading scenarios are asynchronous, so regardless of the lifecycle method used to trigger it, it’s going to need to re-render.