React hooks + old way components = cached state value (😱😱😱)
See original GitHub issuePlease see this demo.
As you can see, I have an old way component looks like this
import React, { Component } from "react";
class OldLibrary extends Component {
componentDidMount() {
setInterval(this.props.onProgress, 50);
}
render() {
return null;
}
}
export default OldLibrary;
A simple component setting interval when mount.
Now I have a component use React Hooks
function App() {
const [value, setValue] = useState(0);
const onProgress = () => {
console.log(value);
// this will always console logging 0 no matter what
};
const onClick = () => {
setValue(value + 1);
// Click me and state changes, but onProgress won't notice
};
return (
<>
<OldLibrary onProgress={onProgress} />
<button onClick={onClick}>Click</button>
</>
);
}
This code above, no matter how I click the button, onProgress will always console logging initial value(which is 0), it’s just like the state is being cached or something I don’t know.
useRef won’t help too.
function App() {
const [value, setValue] = useState(0);
const onProgress = useRef(() => {
console.log(value);
});
const onClick = () => {
setValue(value + 1);
};
return (
<>
<OldLibrary onProgress={onProgress.current} />
<button onClick={onClick}>Click</button>
</>
);
}
But if we use the old way, it will work just fine.
class App extends Component {
state = {
value: 0
};
onClick = () => {
this.setState({ value: this.state.value + 1 });
};
onProgress = () => {
console.log(this.state.value);
// I will console logging different value when state changes
};
render() {
return (
<>
<OldLibrary onProgress={this.onProgress} />
<button onClick={this.onClick}>Click</button>
</>
);
}
}
Thank you for listening my question. Please help, I can’t live without hooks 😭😭😭
Issue Analytics
- State:
- Created 5 years ago
- Comments:11 (1 by maintainers)
Top Results From Across the Web
Accessing previous props or state with React Hooks
Leverage the useRef, useState, usePrevious, and useEffect React Hooks to access previous props and states from within functional components.
Read more >Guide to access previous props or state in React hooks
With class components, we can use the componentDidUpdate method which is called with oldProps and oldState as input.
Read more >Why custom react hooks could destroy your app performance
every state change in a hook will cause its “host” component to re-render, regardless of whether this state is returned in the hook...
Read more >Global Cached State in React using Hooks, Context, and ...
Context Using Hooks We're going to set up a simple context class to store user info (we'll get to local storage later on)....
Read more >How to Add to an Array in React State using Hooks
The previous code example did not work because .push() returns the length of the array after modification, not the array itself. The React...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
@wojtekmaj’s explanation is correct – the onProgress prop gets updated to a new function but it is the old function that was passed to setInterval. In your class component example, onProgress always reads the latest
this.state
which is a mutable field.If you aren’t able to change OldLibrary, your best approach is to store the value in a ref as described in our FAQ: https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback.
Note that this is one of the “sharp edges” of Hooks right now and we might introduce a better way to deal with this before the final release.
With the first render of App, you created onProgress function that returns
value
(0). You are then invokingonProgress
function and it returns 0.You then increment the value of
value
, so that it’s now 1. Within App, now there isvalue
with value of 1, and a newonProgress
function which would return 1 if called.You continue to call the FIRST
onProgress
function in setInterval, but you ignore the fact that now OldLibrary have a SECOND onProgress function passed by props, which returns incremented value ofvalue
.What you can do is wrap your setInterval so it would not call the first given
this.props.onProgress
directly, but an internal method which gets the newestonProgress
function from props and calls it.