Multiple render issues using useState

It is common to write the following code when using hooks, and then find the page rendered twice, sometimes with more headaches.

const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(async() = > {const res = await axios.get("xxx");
  setLoading(false); setData(res); } []);Copy the code

In React, synchronous code merges renderings, while asynchronous code does not.

The following code will render once only, and it will merge the setLoading and setData. This is the same as a class component in that it does not incorporate setState in asynchronous functions.

const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() = > {
  setLoading(false);
  setData({ a: 1}); } []);Copy the code

Class components that handle multiple renders are easier, but hooks are troublesome.

Method 1: Merge multiple states into one state

To solve the problem of multiple renders, put all dependency states into one object and set setState together, as shown in the following code

const [request, setRequest] = useState({ loading: true.data: null });
useEffect(async() = > {const res = await axios.get("xxx");
  setRequest({ loading: false.data: res }); } []);Copy the code

The problem with this is that if you want to setState only one dependency, you need to pass in other dependencies as well, otherwise the value will be lost. React doesn’t do merging for you internally.

setRequest({ data: res }); // The loading value is lost.
Copy the code

The solution is to use extension operators

setRequest({ ... request,data: res });

/ / or
setRequest((prevState) = > ({ ...prevState, data: res }));
Copy the code

Method 2: Write a custom merge dependencyhook

It’s too cumbersome to use the extension operation to match and depend on each setState.

Use the React custom hook function to write a useMergeState hook to merge dependencies.

Custom hooks need to start with use.

const useMergeState = (initialState) = > {
  const [state, setState] = useState(initialState);
  const setMergeState = (newState) = >
    setState((prevState) = > ({ ...prevState, newState }));
  return [state, setMergeState];
};

/ * * / use
const [request, setRequest] = useMegeState({ loading: false.data: null });
useEffect(async() = > {const res = await axios.get("xxx");
  setRequest({ loading: true.data: res });

  // ...

  setRequest({ data: { a: 1}});// The loading state will not be lost} []);Copy the code

Plan 3: UseuseReducer

React provides useReducer to manage dependencies, rather than using useState.

const [request, setRequest] = useReducer(
  (prevState, newState) = > ({ ...prevState, newState }),
  { loading: false.data: null}); useEffect(async() = > {const res = await axios.get("xxx");
  setRequest({ loading: true.data: res });

  // ...

  setRequest({ data: { a: 1}});// The loading state will not be lost} []);Copy the code

If you want to get the previous state, you need to modify the above code.

const [request, setRequest] = useReducer(
  (prevState, newState) = > {
    const newWithPrevState = typeof newState === "function" ? newState(prevState) : newState;
    return{ ...prevState, newWithPrevState })
    },
  { loading: false.data: null}); useEffect(async() = > {const res = await axios.get("xxx");
  setRequest((prevState) = > {
    console.log(prevState)
    return { loading: true.data: res }
  });

  // ...

  setRequest({ data: { a: 1}});// The loading state will not be lost} []);Copy the code

Refer to the article: stackoverflow.com/questions/5…