9.1.60 • Published 1 day ago

@fluentui/react-context-selector v9.1.60

Weekly downloads
6,474
License
MIT
Repository
github
Last release
1 day 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<CounterCountext, 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.60

1 day ago

9.1.59

15 days ago

9.1.58

28 days ago

9.1.57

2 months ago

9.1.56

2 months ago

9.1.55

2 months ago

9.1.54

2 months ago

9.1.53

3 months ago

9.1.52

3 months ago

9.1.51

4 months ago

9.1.50

4 months ago

9.1.49

4 months ago

9.1.48

4 months ago

9.1.47

4 months ago

9.1.46

4 months ago

9.1.45

5 months ago

9.1.44

5 months ago

9.1.43

5 months ago

9.1.28

9 months ago

9.1.29

9 months ago

9.1.26

11 months ago

9.1.27

10 months ago

9.1.39

7 months ago

9.1.35

8 months ago

9.1.36

8 months ago

9.1.37

8 months ago

9.1.38

8 months ago

9.1.31

9 months ago

9.1.32

9 months ago

9.1.33

9 months ago

9.1.34

9 months ago

9.1.30

9 months ago

9.1.42

6 months ago

9.1.40

7 months ago

9.1.41

7 months ago

9.1.25

11 months ago

9.1.24

11 months ago

9.1.23

11 months ago

9.1.17

1 year ago

9.1.18

1 year ago

9.1.19

1 year ago

9.1.13

1 year ago

9.1.14

1 year ago

9.1.15

1 year ago

9.1.16

1 year ago

9.1.11

1 year ago

9.1.12

1 year ago

9.1.20

1 year ago

9.1.21

12 months ago

9.1.22

12 months ago

9.1.10

1 year ago

9.1.9

1 year ago

9.1.8

1 year ago

9.1.7

1 year ago

9.1.6

1 year ago

9.0.5

2 years ago

9.1.5

1 year ago

9.1.4

1 year ago

9.1.3

1 year ago

9.1.2

2 years ago

9.1.1

2 years ago

9.1.0

2 years ago

9.0.4

2 years ago

9.0.3

2 years ago

0.0.0

2 years ago

9.0.2

2 years ago

9.0.1

2 years ago

9.0.0

2 years ago

9.0.0-rc.10

2 years ago

9.0.0-rc.9

2 years ago

9.0.0-rc.7

2 years ago

9.0.0-rc.8

2 years ago

9.0.0-rc.6

2 years ago

9.0.0-rc.5

2 years ago

9.0.0-rc.3

2 years ago

9.0.0-rc.4

2 years ago

9.0.0-rc.1

2 years ago

9.0.0-beta.4

2 years ago

9.0.0-beta.3

3 years ago

9.0.0-beta.2

3 years ago

9.0.0-beta.1

3 years ago

9.0.0-alpha.39

3 years ago

9.0.0-alpha.38

3 years ago

9.0.0-alpha.36

3 years ago

9.0.0-alpha.35

3 years ago

9.0.0-alpha.34

3 years ago

9.0.0-alpha.33

3 years ago

9.0.0-alpha.31

3 years ago

9.0.0-alpha.32

3 years ago

9.0.0-alpha.30

3 years ago

9.0.0-alpha.28

3 years ago

9.0.0-alpha.29

3 years ago

9.0.0-alpha.27

3 years ago

9.0.0-alpha.26

3 years ago

9.0.0-alpha.25

3 years ago

9.0.0-alpha.24

3 years ago

9.0.0-alpha.23

3 years ago

9.0.0-alpha.22

3 years ago

9.0.0-alpha.21

3 years ago

9.0.0-alpha.20

3 years ago

9.0.0-alpha.18

3 years ago

9.0.0-alpha.19

3 years ago

9.0.0-alpha.17

3 years ago

9.0.0-alpha.16

3 years ago

9.0.0-alpha.15

3 years ago

9.0.0-alpha.14

3 years ago

9.0.0-alpha.13

3 years ago

9.0.0-alpha.11

3 years ago

9.0.0-alpha.12

3 years ago

9.0.0-alpha.7

3 years ago

9.0.0-alpha.6

3 years ago

9.0.0-alpha.5

3 years ago

9.0.0-alpha.4

3 years ago

9.0.0-alpha.3

3 years ago

9.0.0-alpha.9

3 years ago

9.0.0-alpha.8

3 years ago

9.0.0-alpha.10

3 years ago

9.0.0-alpha.2

3 years ago

9.0.0-alpha.1

3 years ago

0.53.4

3 years ago

0.52.2-beta.0

3 years ago

0.52.3-beta.0

3 years ago

0.52.2

3 years ago

0.53.3

3 years ago

0.52.1

3 years ago

0.52.1-beta.5

3 years ago

0.53.2

3 years ago

0.53.0

3 years ago

0.52.1-beta.2

3 years ago

0.52.1-beta.1

3 years ago

0.52.1-beta.4

3 years ago

0.52.1-beta.3

3 years ago

0.52.1-beta.0

3 years ago

0.52.0

3 years ago

0.51.7

3 years ago

0.51.6

3 years ago

0.52.0-beta.1

3 years ago

0.52.0-beta.0

3 years ago

0.51.5

3 years ago

0.51.4

3 years ago

0.47.15

3 years ago

0.51.3

3 years ago

0.47.14

3 years ago

0.47.13

4 years ago

0.47.12

4 years ago

0.47.11

4 years ago

0.51.2

4 years ago

0.47.10

4 years ago

0.51.1

4 years ago

0.47.9

4 years ago

0.47.8

4 years ago

0.51.0

4 years ago

0.47.7

4 years ago

0.47.6

4 years ago

0.50.0

4 years ago

0.47.5

4 years ago

0.47.4

4 years ago

0.47.3

4 years ago

0.47.2

4 years ago

0.49.0

4 years ago

0.47.1

4 years ago

0.48.0

4 years ago

0.47.0

4 years ago

0.46.0

4 years ago

0.45.0

4 years ago