简体   繁体   中英

Prevent infinite renders when updating state variable inside useEffect hook with data fetched using useQuery of graphql

Graphql provides useQuery hook to fetch data. It will get called whenever the component re-renders.

//mocking useQuery hook of graphql, which updates the data variable
  const data = useQuery(false);

I am using useEffect hook to control how many times should "useQuery" be called.

What I want to do is whenever I receive the data from useQuery, I want to perform some operation on the data and set it to another state variable " stateOfValue " which is a nested object data. So this has to be done inside the useEffect hook.

Hence I need to add my stateOfValue and " data " (this has my API data) variable as a dependencies to the useEffect hook.

  const [stateOfValue, setStateOfValue] = useState({
    name: "jack",
    options: []
  });

  const someOperation = (currentState) => {
    return {
        ...currentState,
        options: [1, 2, 3]
      };
  }
  useEffect(() => {
    if (data) {
      let newValue = someOperation(stateOfValue);
      setStateOfValue(newValue);
    }
  }, [data, stateOfValue]);

Basically I am adding all the variables which are being used inside my useEffect as a dependency because that is the right way to do according to Dan Abramov.

Now, according to react, state updates must be done without mutations to I am creating a new object every time I need to update the state. But with setting a new state variable object, my component gets re-rendered, causing an infinite renders.

How to go about implementing it in such a manner that I pass in all the variables to my dependency array of useEffect, and having it execute useEffect only once.

Please note: it works if I don't add stateOfValue variable to dependencies, but that would be lying to react.

Here is the reproduced link.

I think you misunderstood what you want to be in dependencies array is [data, setStateOfValue] not [data, stateOfValue] . because you use setStateOfValue not stateOfValue inside useEffect The proper one is:

const [stateOfValue, setStateOfValue] = useState({
    name: "jack",
    options: []
  });

  const someOperation = useCallback((prevValue) => {
    return {
        ...prevValue,
        options: [1, 2, 3]
      };
  },[])
  useEffect(() => {
    if (data) {
      setStateOfValue(prevValue => {
        let newValue = someOperation(prevValue);
        return newValue
      });
    }
  }, [data, setStateOfValue,someOperation]);

If you want to set state in an effect you can do the following:

const data = useQuery(query);
const [stateOfValue, setStateOfValue] = useState({});
const someOperation = useCallback(
  () =>
    setStateOfValue((current) => ({ ...current, data })),
  [data]
);
useEffect(() => someOperation(), [someOperation]);

Every time data changes the function SomeOperation is re created and causes the effect to run. At some point data is loaded or there is an error and data is not re created again causing someOperation not to be created again and the effect not to run again.

First I'd question if you need to store stateOfValue as state. If not (eg it won't be edited by anything else) you could potentially use the useMemo hook instead

const myComputedValue = useMemo(() => someOperation(data), [data]);

Now myComputedValue will be the result of someOperation , but it will only re-run when data changes

If it's necessary to store it as state you might be able to use the onCompleted option in useQuery

const data = useQuery(query, {
  onCompleted: response => {
    let newValue = someOperation();
    setStateOfValue(newValue);
  }
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM