Skip to content

newn-team/use-context-selector

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

use-context-selector

CI npm 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 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.

API

createContext

This creates a special context for useContextSelector.

Parameters

  • defaultValue any

Examples

const PersonContext = createContext({ firstName: '', familyName: '' });

Returns React.Context

useContextSelector

This hook returns context selected value by selector. It will only accept context created by createContext. It will trigger re-render if only the selected value is referentially changed.

Parameters

  • context React.Context
  • selector Function

Examples

const firstName = useContextSelector(PersonContext, state => state.firstName);

Returns any

useContext

This hook returns the entire context value. Use this instead of React.useContext for consistent behavior.

Parameters

  • context React.Context

Examples

const person = useContext(PersonContext);

Returns any

useContextUpdate

This hook returns an update function that accepts a thunk function

Use this for a function that will change a value.

Parameters

  • context

Examples

import { useContextUpdate } from 'use-context-selector';

const update = useContextUpdate();
update(() => setState(...));

BridgeProvider

This is a Provider component for bridging multiple react roots

Parameters

  • props Object
    • props.context React.Context
    • props.value any
    • props.children React.ReactNote

Examples

const valueToBridge = useBridgeValue(PersonContext);
return (
  <Renderer>
    <BridgeProvider context={PersonContext} value={valueToBridge}>
      {children}
    </BridgeProvider>
  </Renderer>
);

Returns React.ReactElement

useBridgeValue

This hook return a value for BridgeProvider

Parameters

  • context React.Context

Returns any

Limitations

  • In order to stop propagation, children of a context provider has to be either created outside of the provider or memoized with React.memo.
  • Provider trigger re-renders only if the context value is referentially changed.
  • 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:01_minimal

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

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

Related projects

Packages

 
 
 

Languages

  • JavaScript 100.0%