React Context

Posted Thursday, March 27, 2025 by Sri. Tagged MEMO
EDITING PHASE:gathering info...

React Context provides a way to make global state available to multiple components, even deeply nested ones. However, it works through sleight of hand that leverages React Hooks.

The basic goal is that when global state changes, all dependent components should update accordingly. React Context provides a roundabout way to do this.

app.js

const MyContext = React.createContext();

function App(props) {
const { children } = props;
const [ something, setSomething ] = React.useState();

const value = {
something,
setSomething
};

return (
<MyContext.Provider value={value}>
{children}
</MyContext.Provider>
)
}
export { MyContext, App }

child.js

import { MyContext } from './app.js';

function MyChild(props) {
const { something } = useContext(MyContext);
return (
<p>{something}</p>
);
}

child-button.js

import { MyContext } from './app.js';

function MyButton(props) {
const { setSomething } = useContext(MyContext);
const handleClick = event => {
setSomething(Date.now());
}
return (
<button onClick={handleClick}>Trigger Change</button>
)
}

Sri's Opinion

  • React Context depends on props changed by useState hooks, which requires carefully constructing a chain of dependencies
  • React Context requires that you use <Context.Provider value={}> as the root component so its specific context is available.
  • React Context relies heavily on arrow functions to bind closures so event handlers still work in functional components, but dynamically-generated components seem to lose the reference to the original context.
  • React Context doesn't help directly with updates. It's an abstraction of a lifecycle that is inferable by intimate knowledge of contruction order, closures, and multiple scopes declared in a single function. If a function with side effects is considered bad, a function with multiple active scopes is probably also bad. This further makes React inaccessible to programmers without an advanced grasp of process control, memory contexts implied by nested functions, and React's specific magical order.

There is nothing good about this other than it is sort of a clever way to represent state, but the boilerplate required is obtuse

Hm, apparently this is due to the behind-the-scenes queueing and ordering of functions; when you use React Hooks, this is actually creating an order-dependent index of functions that hook into the actual rendering lifecycle. Therefore, all event-driven functions have to be wrapped inside a hook declaration.

In a more straightforward implementation, you'd define a lifecycle group

  • calculate
  • detect render state changes
  • update renderlist
  • render
  • run side effects
  • run callbacks

In React, it appears that hooks are used to composite these lists instead:

  • detect changes - this is triggered by state updates performed through a hook like the setStatereturned fromuseState()`, which looks up the stored functional component
  • calculate - done in the body of functional component, which can pull past state through certain hooks like useState
  • update renderlist done in the body of functional component next
  • render performed later by React, using the returned JSX from the functional component
  • run side effects - executed functions were wrapped by useEffect hook, at some point
  • run callbacks - executed function were wrapped by useCallback in the function body

The big takeaway is that you might have to do two things:

  • wrap handlers inside of callback if you need to access hook-defined stuff
  • be aware that the returned function (the render function) is executed outside of the context of the function body...while normal closures are accessible, react context is not as it is dependent on the order of rendering at render time.

NOTES

Context is managed through the createContext() function, which creates a ContextObject that exposes a Context.Provider component that has a prop called value that you setWhile createContext() can receive an initial value, it's never used except when you are paradoxically not using the Provider, which makes it pointless..

  • The wrapper <MyContext.Provider> initializes the context by setting its value property.
  • Deeply-nested children can use useContext(MyContext) to access the current values of the context object, which can contain useState hooks to centralize state within it.

Context objects are not containers; they are used for distributing state accessors only; the Context.Provider's value never changes! The only reason this works with deeply-nested components is because the value contains useState setter/getter functions that do trigger rerenders for any component that uses the getter in its render function.