To start working with Read/Write Context, create a pair of Provider/Consumer components in the same way as for React Context API:
let CounterContext = createContext();
Using <Provider />
you define the root of the state. Once parent is removed,
state disappears as well. This allows maintaining the life cycle of different
states.
function CounterView() {
return (
<CounterContext.Provider>
<CounterOutput />
<CounterHandlers />
</CounterContext.Provider>
);
}
To render the state use <Consumer />
in the same way as you would expect.
function CounterOutput() {
return (
<CounterContext.Consumer>
{value => <p>{value}</p>}
</CounterContext.Consumer>
);
}
In addition to the current state, <Consumer />
provides a method that can be
use for updating the state of the closest <Provider />
.
function CounterHandlers() {
return (
<CounterContext.Consumer>
{(value, setState) => (
<button onClick={() => setState(value + 1)}>
Click to increment
</button>
)}
</CounterContext.Consumer>
);
}
By default, setState()
method of <Consumer />
swaps the state with anything
that it receives as an argument. If a certain logic needed for processing state
updates, createContext()
can receive a reducer function.
Given the counter example above, imagine defining reducer function that allows consumers to trigger state changes via Flux-like "actions".
let CounterContext = createContext(
(state, input) => {
switch (input) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
}
}
);
Since setState()
callback is a positional argument, you're free to call it in
the way that suitable for the purpose:
function CounterHandlers() {
return (
<CounterContext.Consumer>
{(value, dispatch) => (
<React.Fragment>
<button onClick={() => dispatch('INCREMENT')}>
Increment
</button>
<button onClick={() => dispatch('DECREMENT')}>
Decrement
</button>
</React.Fragment>
)}
</CounterContext.Consumer>
);
}
Unlicensed. Feel free to copy the code and readme.