How to read and modify a state in useEffect IAW 'exhaustive-deps' lint rule

后端 未结 4 975
轮回少年
轮回少年 2021-01-25 19:05

I have a map which has an overlay. I have an effect which, when the underlying data changes, deletes the old overlay and re-draws a new one.

Of note, I\'m using \"

相关标签:
4条回答
  • 2021-01-25 19:35

    To add onto @DamienFlury's solution, Another technique you can use to remove a constantly changing dependency from the dependency array is to use the useReducer hook. This allows you to decouple your state variables from your effects

    function reducer(state, action){
      if(action.type === 'UPDATE_OVERLAY'){
        return {
         ...state,
         overlay: action.newOverlay,  //update overlay on your state object
        }
      } else {
        return state;
      }
    }
    
    function YourComponent(yourProps){
      const [ state, dispatch ] = useReducer(reducer, initialState);
    
      // Other functions
    
      useEffect(() => {
        // remove the overlay if it is there,
        if (overlay) map.removeOverlays(overlay);    
    
        // Generate the new overlay based on the new data (useCallback function)
        const newOverlay = generateNewOverlay(data)
    
        // this also allows you to decouple your state changes from your effects!
        dispatch({ 
          type: 'UPDATE_OVERLAY', 
          newOverlay,
        });
    
        // Show the new overlay on my map
        map.addOverlays(newOverlay);
    
      }, [dispatch, map, data, generateNewOverlay]);
    
    }
    

    Note that I have added dispatch to the dependency array just as a good practice, You should never lie about your dependencies!. React guarantees the dispatch function to be constant throughout the component lifetime.

    0 讨论(0)
  • 2021-01-25 19:37

    I think the issue here is that your approach to accommodating this linter rule is too passive. You are running all the code in the useEffect callback any time it runs, but you really only want to execute that stuff when the data changes.

    So you can reflect that in your code:

    const [shouldUpdateOverlay, setShouldUpdateOverlay] = useState(false);
    
    useEffect(() => {
        if (shouldUpdateOverlay) {
            // remove the overlay if it is there,
            // I should have overlay as a dependency per exhaustive-reps react eslint rule
            if (overlay) map.removeOverlays(overlay);    
    
            // Generate the new overlay based on the new data (useCallback function)
            const newOverlay = generateNewOverlay(data);
    
            // Show the new overlay on my map
            map.addOverlays(newOverlay);
    
            // Store the new overlay so I can remove it later if the data changes
            // Doesn't need to be done right away though, just before next render
            setOverlay(newOverlay);
            setShouldUpdateOverlay(false);
        }
    }, [map, overlay, shouldUpdateOverlay data, generateNewOverlay]);
    
    // use from event handlers, etc.
    const updateData = (newData) => {
        setData(newData);
        setShouldUpdateOverlay(true);
    };
    

    Realistically, this is an overwrought approach to accomplishing the same thing you could achieve by having [data] as the only dependency in useEffect, since that's really the only dependency that matters there. However, I believe the above should allow you to achieve what you are trying to do without violating the linter rule.

    0 讨论(0)
  • 2021-01-25 19:42

    I suggest to simply remove it and add the ignore rule with comment. This is what I have my team do and is what is suggested here by Gaearon for specific use-cases.

    Note: I do like @DamienFlury's solution, if it works (and I suspect it does).

    useEffect(() => {
      // remove the overlay if it is there,
      if (overlay) map.removeOverlays(overlay);    
    
      // Generate the new overlay based on the new data (useCallback function)
      const newOverlay = generateNewOverlay(data)
    
      // Store the new overlay so I can remove it later if the data changes
      // Doesn't need to be done right away though, just before next render
      setOverlay(newOverlay);
    
      // Show the new overlay on my map
      map.addOverlays(newOverlay);
      // value of overlay changing should not trigger effect
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [map, data, generateNewOverlay]);
    

    This will stifle the warning, but will also mask any future dependency changes should the API of the effect change, so you're basically on your own to ensure the effect still triggers when you expect it to.

    Does overlay ever change outside this effect? I.E. does your component ever call setOverlay outside the effect?

    0 讨论(0)
  • 2021-01-25 19:53

    You can use a function inside of setOverlay:

    useEffect(() => {
      setOverlay(prev => {
        // remove the overlay if it is there,
        // I should have overlay as a dependency per exhaustive-reps react eslint rule
        if (prev) map.removeOverlays(prev);    
    
        // Generate the new overlay based on the new data (useCallback function)
        const newOverlay = generateNewOverlay(data)
    
        // Show the new overlay on my map
        map.addOverlays(newOverlay);
        return newOverlay;
      });
    
    }, [map, data, generateNewOverlay]);
    

    In the prev parameter you get the current state of overlay. This way you don't have to add the overlay state variable to the dependency array. Hence, the effect won't trigger when overlay updates.

    0 讨论(0)
提交回复
热议问题