Skip to content

Instantly share code, notes, and snippets.

@alexeyraspopov
Last active September 29, 2018 20:56
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexeyraspopov/2027fee1a9c56db4a308da61aaa41453 to your computer and use it in GitHub Desktop.
Save alexeyraspopov/2027fee1a9c56db4a308da61aaa41453 to your computer and use it in GitHub Desktop.

Read/Write React Context

Quick Start

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>
  );
}

Defining Reducer

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>
  );
}

License

Unlicensed. Feel free to copy the code and readme.

import React from 'react';
export default function createContext(reduce = identityReducer, initialState) {
let ReadCtx = React.createContext(initialState);
let WriteCtx = React.createContext();
class Provider extends React.PureComponent {
constructor(props) {
super(props);
this.state = { value: props.value };
this.writer = input => {
this.setState(state => ({
value: reduce(state.value, input),
}));
};
}
render() {
return (
<WriteCtx.Provider value={this.writer}>
<ReadCtx.Provider value={this.state.value}>
{this.props.children}
</ReadCtx.Provider>
</WriteCtx.Provider>
);
}
}
function Consumer({ children }) {
return (
<WriteCtx.Consumer>
{setState => (
<ReadCtx.Consumer>
{value => children(value, setState)}
</ReadCtx.Consumer>
)}
</WriteCtx.Consumer>
);
}
return { Provider, Consumer };
}
function identityReducer(state, input) {
return input;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment