Skip to content
React useContextSelector hook in userland
Branch: master
Clone or download
Latest commit 2820b48 Jul 10, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
__tests__ initial commit Jul 5, 2019
dist revert webpack config, use micorbundle@next Jul 6, 2019
examples minor formatting Jul 11, 2019
src minor change in typing Jul 6, 2019
.eslintrc.json initial commit Jul 5, 2019
.gitignore (chore) - apply pr remarks Jul 6, 2019
.travis.yml add travis yaml Jul 6, 2019
CHANGELOG.md v0.2.0 Jul 6, 2019
README.md update README Jul 10, 2019
package-lock.json v0.2.0 Jul 6, 2019
package.json v0.2.0 Jul 6, 2019
tsconfig.json initial commit Jul 5, 2019
webpack.config.js revert webpack config, use micorbundle@next Jul 6, 2019

README.md

use-context-selector

Build Status npm version bundle size

React useContextSelector hook in userland

Introduction

React Context and useContext is often used to avoid prop drilling, however it's known that there's a performance issue. When a context value is changed, all components that useContext will re-render.

useContextSelector is recently proposed. While waiting for the process, this library provides the API in userland.

Install

npm install use-context-selector

Usage

import React, { useState } from 'react';
import ReactDOM from 'react-dom';

import { createContext, useContextSelector } from 'use-context-selector';

const context = createContext(null);

const Counter1 = () => {
  const count1 = useContextSelector(context, v => v[0].count1);
  const setState = useContextSelector(context, v => v[1]);
  const increment = () => setState(s => ({
    ...s,
    count1: s.count1 + 1,
  }));
  return (
    <div>
      <span>Count1: {count1}</span>
      <button type="button" onClick={increment}>+1</button>
      {Math.random()}
    </div>
  );
};

const Counter2 = () => {
  const count2 = useContextSelector(context, v => v[0].count2);
  const setState = useContextSelector(context, v => v[1]);
  const increment = () => setState(s => ({
    ...s,
    count2: s.count2 + 1,
  }));
  return (
    <div>
      <span>Count2: {count2}</span>
      <button type="button" onClick={increment}>+1</button>
      {Math.random()}
    </div>
  );
};

const StateProvider = ({ children }) => {
  const [state, setState] = useState({ count1: 0, count2: 0 });
  return (
    <context.Provider value={[state, setState]}>
      {children}
    </context.Provider>
  );
};

const App = () => (
  <StateProvider>
    <Counter1 />
    <Counter2 />
  </StateProvider>
);

ReactDOM.render(<App />, document.getElementById('app'));

Technical memo

React context by nature triggers propagation of component re-rendering if a value is changed. To avoid this, this library uses undocumented feature of calculateChangedBits. It then uses a subscription model to force update when a component needs to re-render.

Limitations

  • Subscriptions are per-context basis. So, even if there are multiple context providers in a component tree, all components are subscribed to all providers. This may lead false positives (extra re-renders).
  • In order to stop propagation, children of a context provider has to be either created outside of the provider or memoized with React.memo.
  • Context consumers are not supported.
  • The "stale props" issue can't be solved in userland. (workaround with try-catch)

Examples

The examples folder contains working examples. You can run one of them with

PORT=8080 npm run examples:minimal

and open http://localhost:8080 in your web browser.

You can also try them in codesandbox.io: 01 02

Related projects

You can’t perform that action at this time.