rearm
Rearm
Getting StartedInstallation
Components
CtxIntentUsageExampleDeferredRenderPortalGun
ChangelogReadme

@rearm/ctx

Intent

The Ctx module provides a stable and declarative interface to the concept of "context" in React. It's represented as a single object with a Provider and a use hook as the two sides.

Unlike a typical React context, a component may subscribe to specific parts of the context, or computations based on the context. This has been a very important aspect of react-redux for many years, and can now be leveraged with custom contexts.

Usage

First create your context instance. You may create as many as you like, and there is no interaction between separate contexts, so it's suitable for applications and libraries.

import { makeCtx } from 'rearm/lib/Ctx';
const MyCtx = makeCtx();

If you're using typescript, you may wish to explicitly define a type for the context. As you would expect, both Provider and use will incorporate the generic in their types.

import { makeCtx, Ctx } from 'rearm/lib/Ctx';
const MyCtx: Ctx<{ color: string}> = makeCtx();

Anywhere in the tree you can define the context by rendering a Provider with the value you would like to pass through context. You may have multiple instances of the Provider rendered in different parts of the page, or "shadow" a parent provider if they're nested. A MyCtx.Provider must exist as an (indirect) parent of any component attempting to call MyCtx.use.

render() {
return (
<MyCtx.Provider value={{ color: 'hotpink' }}>
<B />
</MyCtx.Provider>
);
}

You may then access the context by calling Ctx.use with an optional selector function. If not provided, then the entire context value will be returned.

function B() {
const color = Ctx.use(state => state.color);
return <p style={{ color }}>Hello, World!</p>;
}

If you use typescript, then the value of color should be inferred to be string, as we defined the context to be `Ctx<{ color: string }>.

Example

Here we have a few components that share a context with a foreground color, background color, and a callback which can update either.

type Theme = {
fg: string;
bg: string;
update: (key: 'fg' | 'bg', value: string) => void;
};
const ThemeCtx: Ctx<Theme> = makeCtx();

Try pressing the buttons and notice the update counters, which are incremented on each render of the respective components.

Background
Updates: 0
Foreground
Updates: 0
Updates: 0

Etiam commodo diam ut pulvinar tincidunt. Morbi nec erat ac enim pretium posuere et rhoncus urna. Vivamus neque justo, consequat eget neque vitae, dictum hendrerit metus.

Vestibulum faucibus, risus sit amet tincidunt efficitur, turpis lectus aliquam lectus, quis rutrum nibh massa sed metus.

Integer maximus justo sed tincidunt pellentesque. Sed gravida velit vitae est laoreet euismod.

Ut eget accumsan mauris. Aliquam non augue ac metus faucibus faucibus.

Ut et mi et enim ultrices luctus. In quam nulla, egestas id egestas id, blandit eu nisi.

The first section, it accesses the entire context with const theme = ThemeCtx.use(), and as such the containing component will render in reaction to any context update.

The other two components either access the foreground or background color individually.

const fg = ThemeCtx.use((theme) => theme.fg);

The result of evaluating that function doesn't change if only theme.bg is updated, so this component isn't forced to update itself for changes it's unaffected by.

Note that by default, any update in React will cause the entire sub tree to update, so using an update blocking technique like React.memo or React.useMemo is required to limit the natural rendering caused by any state change.

The example uses such a technique in the root component. You normally don't need to worry about this, but if you are having performance issues due to context updates, it's a good thing to try.

function CtxExample(props: {}) {
const [fg, setFg] = React.useState(FG_COLORS[0]);
const [bg, setBg] = React.useState(BG_COLORS[0]);
const theme: Theme = {
fg,
bg,
update(key, value) {
key === 'fg' ? setFg(value) : setBg(value);
},
};
const children = useMemo(
() => (
<div className="rearm-ctx-example">
<Selector />
<Page />
</div>
),
[],
);
return <ThemeCtx.Provider value={theme}>{children}</ThemeCtx.Provider>;
}