use object in useEffect 2nd param without having to stringify it to JSON

后端 未结 3 1708
小蘑菇
小蘑菇 2020-12-30 03:13

In JS two objects are not equals.

const a = {}, b = {};
console.log(a === b);

So I can\'t use an object in useEffect (React ho

相关标签:
3条回答
  • 2020-12-30 03:33

    You could create a custom hook that keeps track of the previous dependency array in a ref and compares the objects with e.g. Lodash isEqual and only runs the provided function if they are not equal.

    Example

    const { useState, useEffect, useRef } = React;
    const { isEqual } = _;
    
    function useDeepEffect(fn, deps) {
      const isFirst = useRef(true);
      const prevDeps = useRef(deps);
    
      useEffect(() => {
        const isFirstEffect = isFirst.current;
        const isSame = prevDeps.current.every((obj, index) =>
          isEqual(obj, deps[index])
        );
    
        isFirst.current = false;
        prevDeps.current = deps;
    
        if (isFirstEffect || !isSame) {
          return fn();
        }
      }, deps);
    }
    
    function App() {
      const [state, setState] = useState({ foo: "foo" });
    
      useEffect(() => {
        setTimeout(() => setState({ foo: "foo" }), 1000);
        setTimeout(() => setState({ foo: "bar" }), 2000);
      }, []);
    
      useDeepEffect(() => {
        console.log("State changed!");
      }, [state]);
    
      return <div>{JSON.stringify(state)}</div>;
    }
    
    ReactDOM.render(<App />, document.getElementById("root"));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    
    <div id="root"></div>

    0 讨论(0)
  • 2020-12-30 03:34

    The above answer by @Tholle is absolutely correct. 2 days back I wrote a post regarding the same on dev.to(https://dev.to/w1n5rx/ways-to-handle-deep-object-comparison-in-useeffect-hook-1elm)

    In React, side effects can be handled in functional components using useEffect hook. In this post, I'm going to talk about the dependency array which holds our props/state and specifically what happens in case there's an object in this array.

    The useEffect hook runs even if one element in the dependency array has changed. React does this for optimisation purposes. On the other hand, if you pass an empty array then it never re-runs.

    However, things become complicated if an object is present in this array. Then even if the object is modified, the hook won't re-run because it doesn't do the deep object comparison between these dependency changes for the object. There are couple of ways to solve this problem.

    1. Use lodash's isEqual method and usePrevious hook. This hook internally uses a ref object that holds a mutable current property that can hold values.

      It’s possible that in the future React will provide a usePrevious Hook out of the box since it is a relatively common use case.

      const prevDeeplyNestedObject = usePrevious(deeplyNestedObject)
      useEffect(()=>{
          if (
              !_.isEqual(
                  prevDeeplyNestedObject,
                  deeplyNestedObject,
              )
          ) {
              // ...execute your code
          }
      },[deeplyNestedObject, prevDeeplyNestedObject])
      
    2. Use useDeepCompareEffect hook as a drop-in replacement for useEffect hook for objects

      import useDeepCompareEffect from 'use-deep-compare-effect'
      ...
      useDeepCompareEffect(()=>{
          // ...execute your code
      }, [deeplyNestedObject])
      
    3. Use useCustomCompareEffect hook which is similar to solution #2

    I prepared a CodeSandbox example related to this post. Fork it and check it yourself.

    0 讨论(0)
  • 2020-12-30 03:44

    Plain (not nested) object in dependency array

    I just want to challenge these two answers and to ask what happen if object in dependency array is not nested. If that is plain object without properties deeper then one level.

    In my opinion in that case, useEffect functionality works without any additional checks.

    I just want to write this, to learn and to explain better to myself if I'm wrong. Any suggestions, explanation is very welcome.

    Here is maybe easier to check and play with example: https://codesandbox.io/s/usehooks-bt9j5?file=/src/App.js

    const {useState, useEffect} = React;
    
    function ChildApp({ person }) {
      useEffect(() => {
        console.log("useEffect ");
      }, [person]);
    
      console.log("Child");
      return (
        <div>
          <hr />
          <h2>Inside child</h2>
          <div>{person.name}</div>
          <div>{person.age}</div>
        </div>
      );
    }
    function App() {
      const [person, setPerson] = useState({ name: "Bobi", age: 29 });
      const [car, setCar] = useState("Volvo");
    
      function handleChange(e) {
        const variable = e.target.name;
        setPerson({ ...person, [variable]: e.target.value });
      }
    
      function handleCarChange(e) {
        setCar(e.target.value);
      }
    
      return (
        <div className="App">
          Name:
          <input
            name="name"
            onChange={(e) => handleChange(e)}
            value={person.name}
          />
          <br />
          Age:
          <input name="age" onChange={(e) => handleChange(e)} value={person.age} />
          <br />
          Car: <input name="car" onChange={(e) => handleCarChange(e)} value={car} />
          <ChildApp person={person} />
        </div>
      );
    }
    
    ReactDOM.render(<App />, document.getElementById("root"));
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-
    dom.development.js"></script>
    <div id="root"></div>

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