Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useEffect Hook Not Firing After State Change

Tags:

I have two sibling components that share state via context in react. The shared state between the components is an array.

If I update arr state in one component, I want the other component to listen for that update and do something accordingly. When I use useEffect in the second component, I listen for changes in the arr state variable.

For example:

// App Component ------- const App = props => {  const { arr, setArr } = useContext(GlobalContext)   const handleChange = () => {    const newArr = arr    [10, 20, 30, 40].map(v => {      newArr.push(v)      setArr(newArr)    })    return (...) }  // App2 (Sibling) Component  const App2 = props => {   const { arr, setArr } = useContext(GlobalContext)   const [localArr, setLocalArr] = useState(0)    useEffect(     () => {       updateLocalState()     },     // fire if "arr" gets updated     [arr]   )    const updateLocalState = () => {     setLocalArr(localArr + 1)   }    return (...) } 

The useEffect hook is only fired on the initial render, though the state of arr updates.

I know that declaring a new variable const newArr = arr to my state variable is a reference, so newArr.push(v) is technically a state mutation. However, the state still updates, no warning is thrown, and useEffect does nothing.

Why does useEffect not get called though the state gets updated? Is it because of the state mutation?

Second Question: Why is there no warning or error thrown regarding a state mutation? State mutations are dangerous - If it happens, I'd expect some sort of warning.

Live demo here:

Edit 7wzlo8y4m1

like image 268
lsimonetti Avatar asked Feb 10 '19 20:02

lsimonetti


People also ask

Does useEffect run after state change?

Use the useEffect hook to listen for state changes in React. You can add the state variables you want to track to the hook's dependencies array and the logic in your useEffect hook will run every time the state variables change.

Does useEffect fire after render?

Note that the function passed to useEffect will be fired only after the DOM changes are painted to the screen. The official docs put it this way, “the function passed to useEffect will run after the render is committed to the screen”.

What is the one problem with using useEffect?

The infinite re-renders problem The reason our component is re-rendering is because our useEffect dependency is constantly changing. But why? We are always passing the same object to our hook! While it is true that we are passing an object with the same key and value, it is not the same object exactly.

Why useState is not updating immediately?

The answer: They're just queues setState , and React. useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.


Video Answer


2 Answers

The array you pass as second argument to useEffect only checks if the elements in the array are === to the elements in it in the previous render. const newArr = arr; will lead to newArr === arr since it doesn't create a new array, which is not what you want.

Create a new array with all the elements in arr and it will work as expected.

const App = props => {  const { arr, setArr } = useContext(GlobalContext)   const handleChange = () => {    const newArr = [...arr]    [10, 20, 30, 40].forEach(v => {      newArr.push(v)    })    setArr(newArr)  }    return <>{/* ... */}</> } 
like image 81
Tholle Avatar answered Sep 20 '22 01:09

Tholle


When you want to update array using useState hook. Make sure to spread the array into new array and update the new array so that your useEffect listening for this state will be called.

UseEffect will not call in the below code snippet as you are directly updating array.

const [skills, selectedSkills] = useState([])       const onSelect = (selectedList) => {             selectedSkills(selectedList)         }      useEffect(() => {             MyLogger('useEffect called')         }, [skills]) 

UseEffect will call in the below code snippet as we are keeping new reference to the array.

const [skills, selectedSkills] = useState([])       const onSelect = (selectedList) => {             const tempSelectedList = [...selectedList]             selectedSkills(tempSelectedList)         }      useEffect(() => {             MyLogger('useEffect called')         }, [skills]) 
like image 30
suresh Avatar answered Sep 20 '22 01:09

suresh