9.1.68 • Published 7 months ago

@fluentui/react-context-selector v9.1.68

Weekly downloads
6,474
License
MIT
Repository
github
Last release
7 months ago

@fluentui/react-context-selector

React useContextSelector() and useContextSelectors() hooks 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 are subscribed with useContext() will re-render.

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

Installation

NPM

npm install --save @fluentui/react-context-selector

Yarn

yarn add @fluentui/react-context-selector

Usage

Getting started

import * as React from 'react';
import { createContext, useContextSelector, ContextSelector } from '@fluentui/react-context-selector';

interface CounterContextValue {
  count1: number;
  count2: number;
  incrementCount1: () => void;
  incrementCount2: () => void;
}

// 💡 The same syntax as native React context API
//    https://reactjs.org/docs/context.html#reactcreatecontext
const CounterContext = createContext<CounterContextValue>({});

const CounterProvider = CounterContext.Provider;

// not necessary but can be a good layer to mock for unit testing
const useCounterContext = <T>(selector: ContextSelector<CounterContextValue, T>) =>
  useContextSelector(CounterContext, selector);

const Counter1 = () => {
  // 💡 Context updates will be propagated only when result of a selector function will change
  //    "Object.is()" is used for internal comparisons
  const count1 = useCounterContext(context => context.count1);
  const increment = useCounterContext(context => context.incrementCount1);

  return <button onClick={increment}>Counter 1: {count1}</button>;
};

const Counter2 = () => {
  const count1 = useCounterContext(context => context.count2);
  const increment = useCounterContext(context => context.incrementCount2);

  return <button onClick={increment}>Counter 1: {count1}</button>;
};

export default function App() {
  const [state, setState] = React.useState({ count1: 0, count2: 0 });

  const incrementCount1 = React.useCallback(() => setState(s => ({ ...s, count1: s.count1 + 1 })), [setState]);
  const incrementCount2 = React.useCallback(() => setState(s => ({ ...s, count2: s.count2 + 1 })), [setState]);

  return (
    <div className="App">
      <CounterProvider
        value={{
          count1: state.count1,
          count2: state.count2,
          incrementCount1,
          incrementCount2,
        }}
      >
        <Counter1 />
        <Counter2 />
      </CounterProvider>
    </div>
  );
}

useHasParentContext

This helper hook will allow you to know if a component is wrapped by a context selector provider

const Foo = () => {
  // An easy way to test if a context provider is wrapped around this component
  // since it's more complicated to compare with a default context value
  const isWrappedWithContext = useHasParentContext(CounterContext);

  if (isWrappedWithContext) {
    return <div>I am inside context selector provider</div>;
  } else {
    return <div>I can only use default context value</div>;
  }
};

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

  • In order to stop propagation, children of a context provider has to be either created outside of the provider or memoized with React.memo.
  • <Consumer /> components are not supported.
  • The stale props issue can't be solved in userland. (workaround with try-catch)

Related projects

The implementation is heavily inspired by:

9.1.68

8 months ago

9.1.67

9 months ago

9.1.66

9 months ago

9.1.64

11 months ago

9.1.65

11 months ago

9.1.61

1 year ago

9.1.62

12 months ago

9.1.63

12 months ago

9.1.60

1 year ago

9.1.59

1 year ago

9.1.58

1 year ago

9.1.57

1 year ago

9.1.56

1 year ago

9.1.55

1 year ago

9.1.54

1 year ago

9.1.53

1 year ago

9.1.52

1 year ago

9.1.51

1 year ago

9.1.50

1 year ago

9.1.49

1 year ago

9.1.48

1 year ago

9.1.47

1 year ago

9.1.46

1 year ago

9.1.45

1 year ago

9.1.44

1 year ago

9.1.43

1 year ago

9.1.28

2 years ago

9.1.29

2 years ago

9.1.26

2 years ago

9.1.27

2 years ago

9.1.39

2 years ago

9.1.35

2 years ago

9.1.36

2 years ago

9.1.37

2 years ago

9.1.38

2 years ago

9.1.31

2 years ago

9.1.32

2 years ago

9.1.33

2 years ago

9.1.34

2 years ago

9.1.30

2 years ago

9.1.42

2 years ago

9.1.40

2 years ago

9.1.41

2 years ago

9.1.25

2 years ago

9.1.24

2 years ago

9.1.23

2 years ago

9.1.17

2 years ago

9.1.18

2 years ago

9.1.19

2 years ago

9.1.13

2 years ago

9.1.14

2 years ago

9.1.15

2 years ago

9.1.16

2 years ago

9.1.11

2 years ago

9.1.12

2 years ago

9.1.20

2 years ago

9.1.21

2 years ago

9.1.22

2 years ago

9.1.10

2 years ago

9.1.9

2 years ago

9.1.8

2 years ago

9.1.7

2 years ago

9.1.6

2 years ago

9.0.5

3 years ago

9.1.5

2 years ago

9.1.4

2 years ago

9.1.3

2 years ago

9.1.2

3 years ago

9.1.1

3 years ago

9.1.0

3 years ago

9.0.4

3 years ago

9.0.3

3 years ago

0.0.0

3 years ago

9.0.2

3 years ago

9.0.1

3 years ago

9.0.0

3 years ago

9.0.0-rc.10

3 years ago

9.0.0-rc.9

3 years ago

9.0.0-rc.7

3 years ago

9.0.0-rc.8

3 years ago

9.0.0-rc.6

3 years ago

9.0.0-rc.5

3 years ago

9.0.0-rc.3

3 years ago

9.0.0-rc.4

3 years ago

9.0.0-rc.1

3 years ago

9.0.0-beta.4

4 years ago

9.0.0-beta.3

4 years ago

9.0.0-beta.2

4 years ago

9.0.0-beta.1

4 years ago

9.0.0-alpha.39

4 years ago

9.0.0-alpha.38

4 years ago

9.0.0-alpha.36

4 years ago

9.0.0-alpha.35

4 years ago

9.0.0-alpha.34

4 years ago

9.0.0-alpha.33

4 years ago

9.0.0-alpha.31

4 years ago

9.0.0-alpha.32

4 years ago

9.0.0-alpha.30

4 years ago

9.0.0-alpha.28

4 years ago

9.0.0-alpha.29

4 years ago

9.0.0-alpha.27

4 years ago

9.0.0-alpha.26

4 years ago

9.0.0-alpha.25

4 years ago

9.0.0-alpha.24

4 years ago

9.0.0-alpha.23

4 years ago

9.0.0-alpha.22

4 years ago

9.0.0-alpha.21

4 years ago

9.0.0-alpha.20

4 years ago

9.0.0-alpha.18

4 years ago

9.0.0-alpha.19

4 years ago

9.0.0-alpha.17

4 years ago

9.0.0-alpha.16

4 years ago

9.0.0-alpha.15

4 years ago

9.0.0-alpha.14

4 years ago

9.0.0-alpha.13

4 years ago

9.0.0-alpha.11

4 years ago

9.0.0-alpha.12

4 years ago

9.0.0-alpha.7

4 years ago

9.0.0-alpha.6

4 years ago

9.0.0-alpha.5

4 years ago

9.0.0-alpha.4

4 years ago

9.0.0-alpha.3

4 years ago

9.0.0-alpha.9

4 years ago

9.0.0-alpha.8

4 years ago

9.0.0-alpha.10

4 years ago

9.0.0-alpha.2

4 years ago

9.0.0-alpha.1

4 years ago

0.53.4

4 years ago

0.52.2-beta.0

4 years ago

0.52.3-beta.0

4 years ago

0.52.2

4 years ago

0.53.3

4 years ago

0.52.1

4 years ago

0.52.1-beta.5

4 years ago

0.53.2

4 years ago

0.53.0

4 years ago

0.52.1-beta.2

4 years ago

0.52.1-beta.1

4 years ago

0.52.1-beta.4

4 years ago

0.52.1-beta.3

4 years ago

0.52.1-beta.0

4 years ago

0.52.0

4 years ago

0.51.7

4 years ago

0.51.6

4 years ago

0.52.0-beta.1

4 years ago

0.52.0-beta.0

4 years ago

0.51.5

4 years ago

0.51.4

4 years ago

0.47.15

5 years ago

0.51.3

5 years ago

0.47.14

5 years ago

0.47.13

5 years ago

0.47.12

5 years ago

0.47.11

5 years ago

0.51.2

5 years ago

0.47.10

5 years ago

0.51.1

5 years ago

0.47.9

5 years ago

0.47.8

5 years ago

0.51.0

5 years ago

0.47.7

5 years ago

0.47.6

5 years ago

0.50.0

5 years ago

0.47.5

5 years ago

0.47.4

5 years ago

0.47.3

5 years ago

0.47.2

5 years ago

0.49.0

5 years ago

0.47.1

5 years ago

0.48.0

5 years ago

0.47.0

5 years ago

0.46.0

5 years ago

0.45.0

5 years ago