Skip to content

Latest commit

 

History

History
212 lines (164 loc) · 5.94 KB

README.md

File metadata and controls

212 lines (164 loc) · 5.94 KB

react-with-multiple-contexts

Why should I use it?

tl;dr to fix "wrapper hell" while using multiple instance of React Context API (16.3+)

You have to admit that the React Context API is an extremely useful, if you need to get props from parent component to child component, and between them is a whole universe of nested things. But this advantage quickly disappears when you need to use more than one context at the same component level.

Click here and you will see the problem I'm talking about

Provider

import React from 'react';
import { ContextA, ContextB, ContextC } from './contexts';

export class ComponentProvider extends React.Component {
  render() {
    return (
      <ContextA.Provider value={this.props.list}>
        <ContextB.Provider value={this.props.config}>
          <ContextC.Provider value={this.props.theme}>
            {this.props.children}
          </ContextC.Provider>
        </ContextB.Provider>
      </ContextA.Provider>
    )
  }
}

Consumer

import React from 'react';
import { ContextA, ContextB, ContextC } from './contexts';

export class ComponentConsumer extends React.Component {
  render() {
    return (
      <ContextA.Consumer>
        {list => (
          <ContextB.Consumer>
          {config => (
            <ContextC.Consumer>
            {themeClass => (
              <React.Fragment>
                <div className={themeClass}>
                  <ol>
                    {list.map(i => (
                      <li key={i}>{i}</li>
                    ))}
                  </ol>
                  <ul>
                    {Object.entries(config).map(([k, v]) => (
                      <li key={k}>{`${k}: ${v}`}</li>
                    ))}
                  </ul>
                </div>
              </React.Fragment>
            )}
          </ContextC.Consumer>
          )}
        </ContextB.Consumer>
        )}
      </ContextA.Consumer>
    )
  }
}

Well, what the react-with-multiple-contexts actually do?

The package provides you a couple of simple HOCs: withContextProvider and withContextConsumer. Each HOC returns a call tree of React.Provider or React.Consumer, depending on what you want to receive. That call tree wrapped around your component. And finally, your component can now use the React Context API via props.

Install

$ npm install react-with-multiple-contexts

Or if you prefer using Yarn:

$ yarn add react-with-multiple-contexts

API

withContextProvider(ReactComponent, callback)

callback is a function that has the props from the Component as an argument and returns a new object that must contain the context and the value that goes into the context.

withContextConsumer(ReactComponent, contexts)

contexts is an object where each property name is the name that you can use in your component through the props and get property value, which is a context value in your component.

Usage Example

Provider Declaration

// componentProvider.jsx

import React from 'react';
import { withContextProvider } from 'react-with-multiple-contexts';
import { contextA, contextB, contextC } from './contexts';

const DummyComponent = (props) => (
  // props also has everything that pass through the context
  // such as props.list, props.config and props.theme
  <React.Fragment>{props.children}</React.Fragment>
);

export const ComponentProvider = withContextProvider(DummyComponent, (props) => ([
  // where each context(A|B|C) it's just an empty React.createContext(null)
  { context: contextA, value: props.list },
  { context: contextB, value: props.config },
  { context: contextC, value: props.theme }
]));

Consumer Declaration

// componentConsumer.jsx

import React from 'react';
import { withContextConsumer } from 'react-with-multiple-contexts';
import { contextA, contextB, contextC } from './contexts';

const DummyComponent = ({ themeClass, list, config }) => (
  <div className={themeClass}>
    <ol>
      {list.map(i => (
        <li key={i}>{i}</li>
      ))}
    </ol>
    <ul>
      {Object.entries(config).map(([k, v]) => (
        <li key={k}>{`${k}: ${v}`}</li>
      ))}
    </ul>
  </div>
};

export const ComponentConsumer = withContextConsumer(DummyComponent, {
  list: contextA,
  config: contextB,
  themeClass: contextC
});

Components Usage

// app.jsx

import React from 'react';
import { ComponentProvider } from './componentProvider';
import { ComponentConsumer } from './componentConsumer';

const App = () => {
  // Technically you can use state here
  // Just pass it to Provider like props
  const justProps = {
    list: [1,2,3],
    config: {
      cool: true,
      boring: false
    },
    theme: 'dark',
  };

  return (
    <ComponentProvider {...justProps}>
      <div className="app">
          <div className="child">
            {/* 
              Consumer below receives everything
              from Provider's props via React.Context API
            */}
            <ComponentConsumer />
          </div>
        </div>
    </ComponentProvider>
  );
}

Future with Hooks

As you know, hooks are an upcoming feature that lets you use state and other React features without writing a class. useContext is one of the hooks, and that hook can simplify consuming value from Context.Consumer with just const context = useContext(Context);. This is even easier than API from this package. But since it is a hook, you can use useContext only in functional components, and still, you need to write nested providers. Although you can easily write something like useProvider(Component, value), but I'm not sure about that yet.

P.S. those HOCs also support Forwarding Refs (16.3+)

License

MIT © Artem Anikeev