React Hooks - useEffect fires even though the state did not change

早过忘川 提交于 2020-12-29 03:35:26

问题


I have set up an effect inside my component, which changes the view if another state attribute changes. But for some reason, when the component mounts, the effect is run, even though the value of detailIndex has not changed.

const EventsSearchList = () => {
    const [view, setView] = useState('table');
    const [detailIndex, setDetailIndex] = useState(null);

    useEffect(() => {
        console.log('onMount', detailIndex);
        // On mount shows "null"
    }, []);


    useEffect(
        a => {
            console.log('Running effect', detailIndex);
            // On mount shows "null"!! Should not have run...
            setView('detail');
        },
        [detailIndex]
    );

    return <div>123</div>;

};

Why is this happening?

UPDATE: In case it is not clear, what I am trying is to run the effect when the component updates because detailIndex changes. NOT when it mounts.


回答1:


useEffect from React Hooks is by default executed on every render, but you can use second parameter in function to define when the effect will be executed again. That means that function is always executed on mount. In your situation your second useEffect will be run on start and when detailIndex changes.

More info: https://reactjs.org/docs/hooks-effect.html

Source:

Experienced JavaScript developers might notice that the function passed to useEffect is going to be different on every render. [...] You can tell React to skip applying an effect if certain values haven’t changed between re-renders. To do so, pass an array as an optional second argument to useEffect: [...]




回答2:


In my case, the component kept updating even though I used the second argument in useEffect() and I was printing the argument to make sure it did not change and it did not change. The problem was that I was rendering the component with map() and there cases where the key changed, and if the key changes, for react it is a completely different object.




回答3:


You can add a second guard, check if detailIndex has changed from the initial value says -1

 useEffect(
    a => {
     if(detailIndex != -1)
      {  console.log('Running effect', detailIndex);
        // On mount shows "null"!! Should not have run...
        setView('detail');
    }},
    [detailIndex]);



回答4:


One way to make sure code only runs on "real" state update and not also on first render, is to assign a variable that is initially false and only becomes true after the first render, and this variable naturally should be a hook, so it will be memo-ed across the component's lifespan.

const {useEffect, useRef, useState} = React

const App = () => {
  const mountedRef = useRef()                 // ← the "flag"
  const [value, setValue] = useState(false)   // simulate a state change 
  
  // with the trick
  useEffect(() => {
      if (mountedRef.current){                // ← the trick
          console.log("trick: changed")
      }
  }, [value])

  // without the trick
  useEffect(() => {
     console.log("regular: changed")
  }, [value])

  // fires once & sets "mountedRef" ref to "true"
  useEffect(() => {
    console.log("rendered")
    mountedRef.current = true
    // update the state after some time
    setTimeout(setValue, 1000, true)
  }, [])
    
  return null
}

ReactDOM.render(<App/>, document.getElementById("root"))
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>



回答5:


useEffect is always ran after the initial render.

From docs:

Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update. (We will later talk about how to customize this.) Instead of thinking in terms of “mounting” and “updating”, you might find it easier to think that effects happen “after render”. React guarantees the DOM has been updated by the time it runs the effects.

Regarding your code, the following will only run once since no dependencies are specified (useEffect takes an optional array of dependencies as a second argument):

useEffect(() => {
  console.log('onMount', detailIndex);
  // On mount shows "null" -> since default value for detailIndex is null as seen above
  // const [detailIndex, setDetailIndex] = useState(null);
}, []);

And this will run only when detailIndex changes (try calling setDetailIndex within the previous useEffect):

useEffect(() =>
  // ... effect code
}, [detailIndex]);

useEffect hook API reference




回答6:


If and only if the useEffect's second parameter state changes by reference (not value), the effect fires.

import React, { useState, useEffect } from 'react';
    function App() {
        const [x, setX] = useState({ a: 1 });
        useEffect(() => {
            console.log('x');
        }, [x]);
        setInterval(() => setX({ a: 1 }), 1000);
        return <div></div>
    }
}
export default App;

In this code snippet, the log is printed in every second, because, every time setX({a: 1}) is called, x state is updated with a new reference to a new object.

If setX({a: 1}) was replaced by setX(1), effect would not fire periodically.



来源:https://stackoverflow.com/questions/54923896/react-hooks-useeffect-fires-even-though-the-state-did-not-change

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!