Heyyyy team,
How's everyone doing? 馃槃
I was trying out React hooks and was rewriting one of my side project but a buggy issue caught me. I created a repro case here.
The case is:
function App() {
let store = {};
const [authStatus, updateAuthStatus] = useState(false);
useEffect(() => {
asyncHelper().then(() => {
store = {
nested: {
value: 'Hello World',
},
}
// people will naturally believe store is updated when auth status is true
// but it's actually not
updateAuthStatus(true);
});
}, []);
if (!authStatus) {
return <h1>Loading...</h1>
}
return (
// will throw an error here (store hasn't been updated)
<h1>{store.nested.value}</h1>
);
}
I'm casting/updating a new value to store in useEffect, assuming that it works similarly with cDM, and then update the authStatus. But the last line when it tries to render <h1>{store.nested.value}</h1>, it'll throw the error "cannot read value of undefined" because store hasn't been updated this time.
It's very natural to have this operation sequence in mind:
However it works as expected if I also wrap the store with a local state:
function App() {
const [store, updateStore] = useState({});
const [authStatus, updateAuthStatus] = useState(false);
useEffect(() => {
asyncHelper().then(() => {
updateStore({
nested: {
value: 'Hello World',
},
})
updateAuthStatus(true);
});
}, []);
if (!authStatus) {
return <h1>Loading...</h1>
}
return (
// will throw an error here (store hasn't been updated)
<h1>{store.nested.value}</h1>
);
}
I'm guessing maybe it's because the async nature of the state update function but not sure if I missed something here or it's a bug.
Have fun in React Conf! I'm so excited that we're finally SHIPPING things! 馃憤
However it works as expected if I also wrap the store with a local state
This looks like expected behavior. If you don't wrap the value with useState there's no way for React to persist updates. When it hits this line:
let store = {};
It will evaluate it just as you'd expect and set store to an empty object. This will happen every time the component renders.
Using the useState hook lets React track and persist the state between renders.
@aweary Emm, but this actually works fine with the previous cDM pattern (confirmed):
componentDidMount() {
this.store = {nested: {value}};
this.setState({
authStatus: true,
});
}
If it's an expected behavior, this means we'll have break change.
The thing is, a function component's body is executed every render, while componentDidMount is only executed on mount, so yes, I'd say this is definitely expected behaviour.
It's not a breaking change because you can still use class components with componentDidMount just fine.
Yeah, if you want to compare it to class components the equivalent would be doing
render() {
// Every time the component re-renders, store gets reset
this.store = {};
That's good explanation. Thanks @aweary and @arianon
Most helpful comment
Yeah, if you want to compare it to class components the equivalent would be doing