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 setState
returned from
useState()`, 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 itsvalue
property. - Deeply-nested children can use
useContext(MyContext)
to access the current values of the context object, which can containuseState
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.