Why is useReducer's dispatch causing re-renders?

夙愿已清 提交于 2020-12-13 12:12:00

问题


Suppose I implement a simple global loading state like this:

// hooks/useLoading.js
import React, { createContext, useContext, useReducer } from 'react';

const Context = createContext();

const { Provider } = Context;

const initialState = {
  isLoading: false,
};

function reducer(state, action) {
  switch (action.type) {
    case 'SET_LOADING_ON': {
      return {
        ...state,
        isLoading: true,
      };
    }
    case 'SET_LOADING_OFF': {
      return {
        ...state,
        isLoading: false,
      };
    }
  }
}

export const actionCreators = {
  setLoadingOn: () => ({
    type: 'SET_LOADING_ON',
  }),
  setLoadingOff: () => ({
    type: 'SET_LOADING_OFF',
  }),
};

export const LoadingProvider = ({ children }) => {
  const [{ isLoading }, dispatch] = useReducer(reducer, initialState);
  return <Provider value={{ isLoading, dispatch }}>{children}</Provider>;
};

export default () => useContext(Context);

Then suppose I have a component that mutates the loading state, but never consumes it, like this:

import React from 'react';
import useLoading, { actionCreators } from 'hooks/useLoading';

export default () => {
  const { dispatch } = useLoading();
  dispatch(actionCreators.setLoadingOn();
  doSomethingAsync().then(() => dispatch(actionCreators.setLoadingOff()))
  return <React.Fragment />;
};

According to useReducer docs, dispatch is has a stable identity. I interpreted this to mean that when a component extracts dispatch from a useReducer, it won't re-render when the state connected to that dispatch changes, because the reference to dispatch will always be the same. Basically, dispatch can "treated like a static value".

Yet when this code runs, the line dispatch(actionCreators.setLoadingOn()) triggers an update to global state and the useLoading hook is ran again and so is dispatch(actionCreators.setLoadingOn()) (infinite re-renders -_-)

Am I not understanding useReducer correctly? Or is there something else I'm doing that might be causing the infinite re-renders?


回答1:


The first issue is that you should never trigger any React state updates while rendering, including useReducers's dispatch() and useState's setters.

The second issue is that yes, dispatching while always cause React to queue a state update and try calling the reducer, and if the reducer returns a new value, React will continue re-rendering. Doesn't matter what component you've dispatched from - causing state updates and re-rendering is the point of useReducer in the first place.

The "stable identity" means that the dispatch variable will point to the same function reference across renders.




回答2:


Besides the fact that you're setting state while rendering as has been pointed out, I think I can shed some light about how to take advantage dispatch's stable identity to avoid unnecessary re-renders like you are expecting.

Your Provider value is an object (value={{ isLoading, dispatch}}). This means the identity of the value itself will change when the context's state changes (for example, when isLoading changes). So even if you have a component where you only consume dispatch like so:

const { dispatch } = useLoading()

The component will re-render when isLoading changes.

If you're at the point where you feel re-rendering is getting out of hand, the way to take advantage of dispatch stable identity is to create two Providers, one for the state (isLoading in this case) and one for dispatch, if you do this, a component that only needs dispatch like so:

const dispatch = useLoadingDispatch()

Will not re-render when isLoading changes.

Note that this can be an overoptimization and in simple scenarios might not be worth it.

This is an excellent set of articles for further reading on the subject: https://kentcdodds.com/blog/how-to-optimize-your-context-value https://kentcdodds.com/blog/how-to-use-react-context-effectively



来源:https://stackoverflow.com/questions/60012198/why-is-usereducers-dispatch-causing-re-renders

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