npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@anion155/selectable-context

v0.3.3

Published

React Context with ability to subscribe to changes in separated field

Readme

React Context with Selector

This library will help to manage renders of the children based on change of one field in the React Context.

When to use it?

When you have setup like this, with multiple values in one Context:

const CoolContext = createContext(undefined);
function CoolProvider({ children }) {
  const [value, setValue] = useState(55);
  const value = useMemo(() => ({ value, setValue, another: 'string' }), []);
  return <CoolContext.Provider value={value}>{children}</CoolContext.Provider>;
}
// ...
<CoolProvider>
  <CoolContext.Consumer>
    {({ value }) => {
      console.log('render with new value:', value);
      return <>This is the `value` value: {value}</>;
    }}
  </CoolContext.Consumer>
  <CoolContext.Consumer>
    {({ setValue }) => {
      console.log('render with new setValue:', setValue);
      return <button onClick={() => setValue(prev => prev + 1)}>Increase value</button>;
    }}
  </CoolContext.Consumer>
  <CoolContext.Consumer>
    {({ another }) => {
      console.log('render with new another:', another);
      return <>This is the `another` value: {another}</>;
    }}
  </CoolContext.Consumer>
</CoolProvider>

In this setup every time field value is changed all consumers are re-rendered, and the same goes for the changes of another field.

How to use it?

Let's take same example and rewrite it without this unnecessary re-renders. It's pretty similar to React Context:

import { createSelectableContext } from "@anion155/selectable-context";
// Here we are using library context implementation
const CoolContext = createSelectableContext(undefined);
function CoolProvider({ children }) {
  const [value, setValue] = useState(55);
  const value = useMemo(() => ({ value, setValue, another: 'string' }), []);
  return <CoolContext.Provider value={value}>{children}</CoolContext.Provider>;
}
// ...
<CoolProvider>
  {/* Here we declare selector for the value */}
  <CoolContext.Consumer selector={({ value }) => value}>
    {(value) => {
      console.log('render with new value:', value);
      return <>This is the `value` value: {value}</>;
    }}
  </CoolContext.Consumer>
  {/* Here we declare selector for the value */}
  <CoolContext.Consumer selector={({ setValue }) => setValue}>
    {(setValue) => {
      console.log('render with new setValue:', setValue);
      return <button onClick={() => setValue(prev => prev + 1)}>Increase value</button>;
    }}
  </CoolContext.Consumer>
  {/* Here we declare selector for the value */}
  <CoolContext.Consumer selector={({ another }) => another}>
    {(another) => {
      console.log('render with new another:', another);
      return <>This is the `another` value: {another}</>;
    }}
  </CoolContext.Consumer>
</CoolProvider>

Now when we will change value using setValue only first Consumer would be called for render.

Quick tip

With this setup we still need to separate render of children from render of Provider, whether by using React.memo on children Components or by rendering children separately (separate Provider component with it's inner state, and pass rendered elements through children prop)

API

createSelectableContext

Is a replacement of React.createContext and has similar api. Only exception is that Consumer now accepts selector property, which would select value passed to children function

function createSelectableContext<T>(defaultValue: T): SelectableContext<T>;

type SelectableProviderProps<T> = {
  value: T;
  children?: React.ReactNode | undefined;
};
type SelectableConsumerProps<T, R = T> = {
  selector?: (value: T) => R;
  children: (selected: R) => React.ReactNode;
};
type SelectableContext<T> = {
  Provider: FC<SelectableProviderProps<T>>;
  Consumer: <R = T>(props: SelectableConsumerProps<T, R>) => ReactElement;
  defaultValue: T;
};

Note

Created Context is not compatible with React.useContext and should be used with the following hook

useSelectableContext

This hook accepts Context instance with optional selector and isEqual, and return it's current value as well as subscribes to value changes. It is using useSyncExternalStoreWithSelector internally, so it does support minimum memoization of selector:

function useSelectableContext<T>(Context: SelectableContext<T>): T;
function useSelectableContext<T, R>(
  Context: SelectableContext<T>,
  selector: (value: T) => R,
  isEqual?: IsEqualBinary<T, R>
): R;
function useSelectableContext<T, R>(
  Context: SelectableContext<T>,
  selector?: (value: T) => R,
  isEqual?: IsEqualBinary<T, R>
): T | R;

type IsEqualBinary<T, R> = (
  a: T | NonNullable<R>,
  b: T | NonNullable<R>
) => boolean;

Internal API

Internally SelectableContext uses React.Context and passes Observable-like object with set of listeners and subscription method:

type SelectableContextController<T> = {
  value: T;
  listeners: Set<Listener<T>>;
  subscribe: Subscription<T>;
};

It is possible to gather this controller by using useSelectableContextController hook (available only as a source code)

import { useSelectableContextController } from "@anion155/selectable-context/internal";

const controller = useSelectableContextController(CoolContext);
console.log('Count of listeners on this render:', controller.listeners.size);

Note

Subscription of listener or change of context value would not mark component using this hook for re-render. Controller is created once for lifecycle of the component.