3

Some blog articles split up the context into two seperate update and state context when working with React Context. I am wondering whether this really increases the performance or can lead to reduced render cycles.

So the final solution will have:

  • two different context objects
  • two different hooks for 1.) accessing the state and 2.) modifying the state
  • two seperate providers (wrapped into one single one like in AuthProvider)

Solution splitting up Update and State Context:

const authContext = React.createContext<AuthUser | null | undefined>(undefined)
const authUpdateContext = React.createContext<Dispatch<SetStateAction<AuthUser | null>> | null>(null)

export function useAuth() {
    const authUser = React.useContext(authContext);
    if (authUser === undefined) throw new Error(`useAuth must be used within a ContextAuthProvider`);
    return authUser;
}

export const useAuthUpdate = () => {
    const setAuthUser = React.useContext(authUpdateContext);
    if (!setAuthUser) throw new Error(`useAuthUpdate must be used within a AuthProvider`);
    return setAuthuser;
}

export const AuthProvider: React.FC = ({children}) => {
    const [authUser, setAuthUser] = useState<AuthUser | null>(null)
    return (
        <authUpdateContext.Provider value={setAuthUser}>
            <authContext.Provider value={authUser}>
                {children}
            </authContext.Provider>
        </authUpdateContext.Provider>
    )
}

// Usage only in components where needed (one of those two or both combined)
const authUpdate = useAuthUpdate()
const auth = useAuth()
mleister
  • 1,697
  • 2
  • 20
  • 46
  • 2
    The actions of the context tend not to change, where as the state those actions control does. If you don't split them, every time the state changes a render will be triggered on all components that subscribe to the context even if they only access the actions which probably haven't changed. Here's a related question: [Avoid runnning an effect hook when Context get updated](https://stackoverflow.com/questions/66543656/avoid-runnning-an-effect-hook-when-context-get-updated/66544925#66544925) – pilchard Mar 20 '21 at 01:43
  • Could you please check the answer? – mleister Mar 20 '21 at 13:52
  • The answer seems to miss what you are doing. You can definitely save renders (and related calls such as useEffect) on components that only subscribe to the actions context. The related question I linked clearly shows this. If only one context was used in that example the useEffect in the ActionsComponent would fire on every tick of the counter in the ValueComponent, but it doesn't because each component is subscribing to a different context. – pilchard Mar 20 '21 at 14:00
  • Thanks, do you want to put that in an answer so other people can quickly see the correct answer? – mleister Mar 20 '21 at 14:46

2 Answers2

4

The actions of the context tend not to change, whereas the state those actions control does. If you create a single context that serves both state values and state update methods, every time the state value changes a render will be triggered on all components that subscribe to the context even if they only access the actions (which probably haven't changed).

You can avoid this and save renders (and related side-effects such as useEffect() calls) on components that only subscribe to the actions context by splitting the context into two separate contexts as in your example.

Here is a related question: Avoid runnning an effect hook when Context get updated that clearly illustrates this. If only one context was used in that example the useEffect in the ActionsComponent would fire on every tick of the counter in the ValueComponent, but it doesn't because each component is subscribing to a different context.

And here is a concise rundown of your options in an issue on the React repo: Preventing rerenders with React.memo and useContext hook. #15156: Option 1 (Preferred): Split contexts that don't change together

pilchard
  • 12,414
  • 5
  • 11
  • 23
  • yes but splitting the context in two doesn't prevent *rerenders* of children components, your [example](https://stackblitz.com/edit/react-zpxzcy?file=src%2FApp.js), see the log, it prints both "set" and "view". No? – Giorgi Moniava Feb 19 '23 at 15:59
-2

The important thing you need to understand is any components subscribed to a particular context re-renders when the context's value changes. Going back to your question, it doesn't make sense to use a different hook for updating and retrieving the value of the state since both are connected to the context provider. I hope this makes sense. More details here

Jasper Bernales
  • 1,601
  • 1
  • 11
  • 16
  • The OP is declaring two separate contexts and passing the state value in one, and the state modifiers in the other. These are separate contexts and changing the value on the value context will have no affect on the actions context, thus the components subscribing to the actions context will not have renders triggered on value change. – pilchard Mar 20 '21 at 14:02