tldr; How do I simulate componentDidUpdate
or otherwise use the key
prop with an array to force my component to be reset?
use a custom hook
export const useComponentDidUpdate = (effect, dependencies) => {
const hasMounted = useRef(false);
useEffect(
() => {
if (!hasMounted.current) {
hasMounted.current = true;
return;
}
effect();
},
dependencies
);
};
Effect will not run after the initial render. Thereafter, it depends on the array of values that should be observed. If it's empty, it will run after every render. Otherwise, it will run when one of it's values has changed.
In short, you want to reset your timer when the reference of the array changes, right ?
If so, you will need to use some diffing mechanism, a pure hooks based solution would take advantage of the second parameter of useEffect
, like so:
function RefresherTimer(props) {
const [startedAt, setStartedAt] = useState(new Date());
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));
//reset part, lets just set startedAt to now
useEffect(() => setStartedAt(new Date()),
//important part
[props.listOfObjects] // <= means: run this effect only if any variable
// in that array is different from the last run
)
useEffect(() => {
// everything with intervals, and the render
})
}
More information about this behaviour here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
A way to remount a component is to provide new key
property. It's not necessarily a string but it will be coerced to a string internally, so if listOfObjects
is a string, it's expected that key
is compared internally with listOfObjects.toString()
.
Any random key can be used, e.g. uuid
or Math.random()
. Shallow comparison of listOfObjects
can be performed in parent component to provide new key. useMemo
hook can be used in parent state to conditionally update remount key, and listOfObjects
can be used as a list of parameters that need to be memoized. Here's an example:
const remountKey = useMemo(() => Math.random(), listOfObjects);
return (
<div>
<RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
</div>
);
As an alternative to remount key, child component could be able to reset own state and expose a callback to trigger a reset.
Doing shallow comparison of listOfObjects
inside child component would be an antipattern because this requires it to be aware of parent component implementation.
The useRef
creates an "instance variable" in functional component. It acts as a flag to indicate whether it is in mount or update phase without updating state.
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
// do componentDidMount logic
mounted.current = true;
} else {
// do componentDidUpdate logic
}
});