A Secret parts of React New Context API

Toru Kobayashi
3 min readApr 3, 2018

--

New Context API

React introduced a new Context API, which is to replace a legacy Context API.

The API is using a render props pattern so you can use the API flexible.

const LangContext = React.createContext(null);const App = () => (
<LangContext.Provider value="en">
<LangContext.Consumer>
{lang =>
<Message text="hello" lang={lang} />
}
</LangContext.Consumer>
</LangContext.Provider>
);

With new Context API, you can create a global store like Redux.

const store = {
state = {
foo: 1,
bar: 1,
},
update(cb) {
this.state = cb(this.state);
}
};
const {Provider, Consumer} = React.createContext(store.state);const Foo = () => (
<Consumer>
{({foo, update}) => (
<div>
<button
onClick={() =>
update((state) => ({...state, foo: state.foo + 1}))
}
>
click
</button>
<p>Foo is {foo}</p>
</div>
)}
</Consumer>
);
const Bar = () => (
<Consumer>
{({bar, update}) => (
<div>
<button
onClick={() =>
update((state) => ({...state, bar: state.bar + 1}))
}
>
click
</button>
<p>Bar is {bar}</p>
</div>
)}
</Consumer>
);
const App = () => (
<Provider value={store.state}>
<Foo />
<Bar />
</Provider>
);

It’s cool, isn’t it? But the above example has a problem. If you click the Foo’s button, Foo and Bar are re-rendered regardless of which values are updated.

In order to solve the problem, the New Context API has a secret feature of observedBits.

ObservedBits

The New Context API is able to optimize your updates for a context value, in order to do this, you have to specify changedBits and observedBits.

Let’s take a look.

changedBits

changedBits are bits represents what context values are changed. You can specify a function returning changedBits as the 2nd argument of React.createContext. the function receives the previous value and the current value.

const store = {
observedBits: {
foo: 0b01,
bar: 0b10
},
state = {
foo: 1,
bar: 1,
},
update(cb) {
this.state = cb(this.state);
}
};
const StoreContext = React.createContext(
store.state,
(prev, next) => {
let result = 0;
// foo has been changed
if (prev.foo !== next.foo) {
result |= store.observedBits.foo;
}
// bar has been changed
if (prev.bar !== next.bar) {
result |= store.observedBits.bar;
}
return result;
}
);

The above the implementation, if foo has been changed, the function returns 0b01, if bar has been changed, it returns 0b10. If both have been changed the function returns 0b11.

unstable_observedBits

observedBits are bits represents what values Consumer are observed. You can specify the observedBits as the Consumer’s Props.

const Foo = () => (
<Consumer unstable_observedBits={store.observedBits.foo}>
{({foo, update}) => (
<div>
<button
onClick={() =>
update((state) => ({...state, foo: state.foo + 1}))
}
>
click
</button>
<p>Foo is {foo}</p>
</div>
)}
</Consumer>
);
const Bar = () => (
<Consumer unstable_observedBits={store.observedBits.bar}>
{({bar, update}) => (
<div>
<button
onClick={() =>
update((state) => ({...state, bar: state.bar + 1}))
}
>
click
</button>
<p>Bar is {bar}</p>
</div>
)}
</Consumer>
);

You can specify the observedBits as the unstable_observedBits Props. This is a unstable feature so the Props has unstable_ prefix. When Consumer Component is passed unstable_observedBits Props, Consumer is re-rendered only when unstable_observedBits & changedBits !== 0.

If you are not familiar with bitwise operators, check the MDN references.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators

Please check the example.

React Context observedBits example

BTW, React uses bitwise operators in its Fiber implementation, which is very interesting :)

React Fiber’s rendering mode is represented by bits.

export const NoContext = 0b00;
export const AsyncMode = 0b01;
export const StrictMode = 0b10;

React Fiber’s side effects also are represented by bits, which is possible to mask the effects by bitmasks (See the HostEffectMask).

export const NoEffect = /*              */ 0b000000000000;
export const PerformedWork = /* */ 0b000000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b000000000010;
export const Update = /* */ 0b000000000100;
export const PlacementAndUpdate = /* */ 0b000000000110;
export const Deletion = /* */ 0b000000001000;
export const ContentReset = /* */ 0b000000010000;
export const Callback = /* */ 0b000000100000;
export const DidCapture = /* */ 0b000001000000;
export const Ref = /* */ 0b000010000000;
export const ErrLog = /* */ 0b000100000000;
export const Snapshot = /* */ 0b100000000000;
// Union of all host effects
export const HostEffectMask = /* */ 0b100111111111;
export const Incomplete = /* */ 0b001000000000;
export const ShouldCapture = /* */ 0b010000000000;

--

--