React Context: a Hidden Power
Tags: webdev, javascript, react
Published: 2020-05-25 16:05:49

Last week I had to implement the new React Context API for a React 15 project. Migrating to React 16 was not the option due to a big codebase so I headed to React sources for references.
The first thing I noted was [the second argument](https://github.com/facebook/react/blob/c390ab3643612dc08ca4bebadc5b0377e9e7eb79/packages/react/src/ReactContext.js#L14) of the `createContext` function:
```typescript
export function createContext<T>(
  defaultValue: T,
  calculateChangedBits: ?(a: T, b: T) => number,
): ReactContext<T> {
```
The second argument is not mentioned in the React docs so started discovering what that is. After some investigation I found out that there's an optimization that can be applied to React Context.

#So what does it actually do?
React Context allows its consumers to observe certain bits of a bitmask produced by the `calculateChangedBits` function that can be passed as the second argument to `createContext`. If one of the observed bits changes, a context consumer gets re-rendered. If not, it's not going to do an unneeded re-render. Sounds great! Let's see how it works in practice.

#Before we start
If you're not familiar with bitwise operators, check out [this MDN page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators).

#A Sample App
I created a simple Ice Cream Constructor app that has two selects and shows a list of available options based on selected values. The filter is a simple React Context that holds the state of selected values and provides an API for its consumers to get a current filter state and update it. You can check out the full demo [here](https://stackblitz.com/edit/react-context-bitmask).

First of all, let's define an object that's going to map context consumers to bits they observe:
```javascript
export default {
  fruit: 0b01,
  topping: 0b10,
};
```
`0b` is a binary prefix meaning that a number following after it is binary. By putting 1s and 0s we tell what bits are going to be observed. There won't be any observed bits if we put 0, and every bit is observed if we put all 1s. In our example we say that fruit is going to observe the first bit and topping is going to observe the second bit.

###calculateChangedBits
Now Let's create a filter context:
```javascript
import React from 'react';
import observedBitsMap from './observedBitsMap';

const calculateChangedBits = (currentFilter, nextFilter) => {
  let result = 0;

  Object.entries(nextFilter.filter).forEach(([key, value]) => {
    if (value !== currentFilter.filter[key]) {
      result = result | observedBitsMap[key];
    }
  });
  
  return result;
};

const initialValue = {
  filter: {
    fruit: 'banana',
    topping: 'caramel',
  },
};

export const FilterContext = React.createContext(initialValue, calculateChangedBits);
```
`calculateChangedBits` is passed as the second argument to `React.createContext`. It takes current context value and new context value and returns a value that represents changed context values that are changed.

###unstable_observedBits
While result of calling `calculateChangedBits` represents the whole change, `unstable_observedBits` tells what particular bits of the whole change are going to trigger a context consumer update. It's passed as the second argument to `React.useContext`:
```javascript
import React from 'react';
import observedBitsMap from './observedBitsMap';
import { FilterContext } from './FilterContext';

const FilterItem = ({ name, children }) => {
  const context = React.useContext(FilterContext, observedBitsMap[name]);

  const onChange = React.useCallback(
    (e) => {
      context.onFilterChange(e);
    },
    [context.onFilterChange],
  );

  return children({ name, onChange, value: context.filter[name] });
}
```
If you want to use a regular JSX `Context.Consumer` you can pass `unstable_observedBits` as a prop:
```jsx
<FilterContext.Consumer unstable_observedBits={observedBitsMap[name]}>
...
```
If `unstable_observedBits` is passed, consumer is going to be updated only if the result of bitwise `AND` on what we got from `calculateChangedBits`'s execution and `unstable_observedBits` is not equal to 0.

Let's see how it works:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qla78eib90hd0b2jkf22.gif)


#Limitations
As you can see from the `unstable_observedBits` name, this is an unstable experimental feature. Every time a context value changes React shows a warning:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tcwm70uomah6lk673kfr.gif)

Also, there's a limitation on the number of bits that can be observed. It is restricted by the [max integer size in V8 for 32-bit systems](https://github.com/facebook/react/blob/master/packages/react-reconciler/src/MaxInts.js). This means that we can't effectively re-render observe more than 30 different consumers.


#Conclusion
Even though React Context API provides a great optimization opportunity, I don't think it should be widely used. This whole thing is more about exploring what the library hides rather than finding something for usual usage. If you think that you want to apply this optimization in your project, ask yourself "why are my renders so slow that I need to use a deep optimization?" question first.

I guess that this feature is going to be used mostly in libraries even when it turns to stable. But I'm really interested in what direction would the implementation evolve.