Info |
---|
I was putting together some code to demonstrate how modifying a state variable causes a component to rerender. All was going will until I put a timer on the component, that got the current time and displayed it in the page. That worked fine example 1, but it all changed when I wanted to keep a list of all the times and print them all out each time a new time was added, things a got a little weird, example 2. |
...
Event | What’s executed in order shown | ||||
---|---|---|---|---|---|
Initial loading of the component Notice that both these useEffects() witht with the lambda and empty array are called once and only once |
| ||||
Component needs to be re-rendered Notice that the component method is first called, then it is unmounted, then is finally re-rendered through the component return() block (you can see this because the useEffect() with only the lambda is called |
|
...
If you want data to persist across component renders, you need to use useRef()
. We'll cover this in another section
Example 2
All of the above explains why the following code will not run as expected if you don’t use the useRef() callback
Code Block | ||
---|---|---|
| ||
import React, { useState } from 'react'
import { useEffect } from 'react';
import { useRef } from 'react';
function getTheTime()
{
let currentDate = new Date();
let currentTime = currentDate.getHours() + ":" + currentDate.getMinutes() + ":" + currentDate.getSeconds();
return currentTime;
}
export default function ContainerFcWuE(){
const [simpleField, setSimpleField] = useState('');
const [containerState, setContainerState] = useState([getTheTime()]);
/* Without these two lines below, this code will not work as expected
* The problem is, every time a state field changes the function is
* is re-executed in it's entirety, so the state fields are completely
* reset. I know I was suprised by this fact!
*
* useRef allows you to create a mutable value that does not cause a
* re-render when it is changed.
*
* If you remove containerRef and containerRef.current and replace
* containerRef.current with containerState, you will see the problem.
*
* A description of this problem and a solution can be found at
* https://stackoverflow.com/questions/56511176/state-being-reset
*/
const containerRef = useRef([]);
containerRef.current = containerState;
const addToList = name => {
console.log(`Current array: ${containerRef.current}`);
const update = [...containerRef.current, name];
console.log(`Updated array: ${update}`);
setContainerState(update);
}
const addTimeToList = () => {
addToList(getTheTime())
};
const getDisplayData = () => {
const cdata = containerState.map((item, index) =>
<li key={index}>{item}</li>);
return cdata;
}
// This useEffect will only run once when the compone is mounted
useEffect(()=>{
setInterval(()=>{
addTimeToList()
}, 10000);
}, [])
// This useEffect will run every time the component re-renders
useEffect(() =>{
console.log(getDisplayData())
})
// This useEffect will run every time the simpleField changes
useEffect(() =>{
console.log(simpleField);
}, [simpleField]);
// This useEffect will run every time the component is unmounted
useEffect(() =>{
return ()=>console.log("unmounting the ContainerFcWuE");
});
return (
<>
Type something (open dev tools/console to see behaviour)<input type='text' value={simpleField} onChange={(e) => setSimpleField(e.target.value)} />
<div onClick={e => setSimpleField("YYY")}>
<h2>[fcwue] Click Me</h2>
</div>
<ul>{getDisplayData()}</ul>
</>
)
} |