skip to Main Content

Imagine I have some custom hook where I extract some value from context ( some kind of my own store )

// example of state structure

function GlobalComponent(){
 const initialState = {
   reports: {depositReport: []},
   users: [],
 }

 const [state, dispatch] = useReducer(reducer, initialState)

 return <StoreContext.Provider value={{state}}></State.Provider>
}
const useSelector = (callback: (state: State) => State) => {
  const { state } = useContext(StoreContext);

  return callback(state);
};

and here is example of how I use this hook

function Component() {
 const { depositReport } = useSelector(state => state.reports);
}

when I extract depositReport value from state by example above in Component , it will rerender each time any property changes in state object. But how can I optimize useSelector hook , to rerender component when it used only when particular value state => state.reports will be changed .

Check example on codesandbox, and you will see that when I change users property , it rerenders place where I only extract reports.depositReport but I want optimiza useSelector hook in way , it will only rerender when I extract value on which change I want to react

Edit late-monad-c6pt8l

2

Answers


  1. It is impossible this way. It uses context which value changes on each render. If that value changes, every children component that use useContext(StoreContext) rerenders. There is no way to prevent that.

    What you can do is to use a third party library like use-context-selector or create your own store. It is not that simple as just calling useContext

    Login or Signup to reply.
  2. So based on the provided sandbox, I was able to find two key places where you can reduce unnecessary re-renders:

    • The useSelector function
    • The Users component where you are subscribing to the state

    Here are the implemented changes:

    const useSelector = (callback) => {
      const { state } = useContext(StoreContext);
      return useMemo(() => callback(state), [callback, state]);
    };
    

    Here, the use of useMemo will recompute the selected part of the state if either the callback function or the state object changes. This, however, requires that you wrap the callback function in a useCallback hook whenever you call it from your components, hence the updated Users component:

    const Users = () => {
      const dispatch = useDispatch();
      const selectUsers = useCallback((state) => state.users, []);
      const users = useSelector(selectUsers);
    
      console.log(users, "users");
      return (
        <div>
          <button
            onClick={() => {
              const data_to_set = ["first", "second"];
              if (areArraysEqual(users, data_to_set)) return;
              dispatch({
                type: UsersE.SET_USERS,
                payload: {
                  data: data_to_set
                }
              });
            }}
          >
            Change users
          </button>
        </div>
      );
    };
    

    The definition of areArraysEqual:

    function areArraysEqual(arr1, arr2) {
      if (arr1.length !== arr2.length) return false;
      const sortedArr1 = arr1.slice().sort();
      const sortedArr2 = arr2.slice().sort();
    
      for (let i = 0; i < sortedArr1.length; i++) {
        if (sortedArr1[i] !== sortedArr2[i]) {
          return false;
        }
      }
    
      return true;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search