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 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-context-helper

v1.5.3

Published

Tiny library that provides easy updating of context and optimization of context consumers.

Downloads

17

Readme

npm

react-context-helper

This tiny library includes:

  • ContextProvider and ContextReducerProvider : Custom Context Providers that help you easily update your React context from consumers in a standard way. Works exactly like a regular Context.Provider but adds the methods updateContext and removeFromContext (alternatively, a dispatch method) to the context consumers get.

  • useMemoConsumer A custom hook that allows you to optimize heavier consumers that would otherwise always update when the context they consume updates, even if the context properties they consume don't change.

The components and hook can be used independently or together.

Note: Currently supports regular object context types only.

Install:

npm i react-context-helper

yarn add react-context-helper

What it solves

1) The Context API doesn't provide a simple and/or standard way to update context

2) Context can cause unnecessary rerenders

Context consumers get rerendered when the context is updated -- even when they don't use the part of the context that has changed.

E.g., given the following initial context value,

{ foo: "bar", fizz: "buzz", updateFizz: () => {...} }

the following component will rerender when the context is updated by running updateFizz (e.g., from another consumer). Note that only fizz, not foo, is updated. This component will rerender even though it only uses foo.

const Consumer = () => {
  const { foo } = useContext(Context);
  return <div>{foo}</div>;
};

This can cause a lot of undesired rendering time.

The ideal way to avoid this problem is to create multiple contexts, ensuring that each consumer needs all entries in the context it consumes. If this isn't possible, an optimization can be made using useMemoConsumer.

Tests confirm that optimizing both light and heavy components with useMemoConsumer increases performance. This is clearly more pronounced with heavier components.

ContextProvider API

Example Usage

import { createContext, useContext } from "react";
import { ContextProvider } from "react-context-helper";

const context = createContext({});

const Consumer = () => {
  const { message, updateContext, removeFromContext } = useContext(context);

  /* changes context to { message: "hello context!", fizz: "buzz"}(plus update functions) */
  updateContext({ message: "hello context!" });
  removeFromContext(["foo"]);

  // output: hello context!
  return <div>{message}</div>;
};

const App = () => {
  return (
    <ContextProvider
      contextObj={context}
      value={{ message: "hello world", foo: "bar", fizz: "buzz" }}
    >
      <Consumer />
    </ContextProvider>
  );
};

Props

  • value: Object
    Identical to the value prop for any Context.Provider, except for the (current) requirement that it be a regular object.

  • contextObj: Context
    The React Context that the consumer will be consuming.

    Example:

//snippet from Context.js
const initial = { foo: "bar", fizz: { buzz: { fizz: "buzz" } } };
const context = createContext(initial);
export default context;
//

//snippet from App.js
import context from "path/to/context";

const App = () => {
  return (
    <ContextProvider contextObj={context} value={initialValue}>
      <ChildWithConsumers />
    </ContextProvider>
  );
};
//

Functions added to the context object

The context value is assumed to be a regular object. An upcoming version will work for arrays and primitives as well.

The context provided by ContextProvider includes two methods which allow you to modify your context as necessary:

(Note: these functions wrap setState calls, which are asynchoronous.)

updateContext(updateObject)

Parameters:

  • updateObject: Object
    an Object to merge with the current context. Any properties that are already in the context object are overriden, and any properties that aren't are added.

Return value:

  • void
//snippet from Context.js
const initial = { foo: "bar", fizz: { buzz: { fizz: "buzz" } } };
const context = createContext(initial);
export default context;
//

//snippet from Consumer.js
import context from "path/to/context";
const consumedContext = useContext(context);

/*
changes context to
{ foo: "bar", fizz: { buzz: "fizz"}, bar: "foo" }
(plus update functions) */
consumedContext.updateContext({ fizz: { buzz: "fizz" }, bar: "foo" });

//

removeFromContext(keyArray)

Parameters:

  • keyArray: Array<string>:
    an array of keys (strings) to properties that will be removed from the context object.

Return value:

  • void
//snippet from Context.js
const initial = { foo: "bar", fizz: { buzz: { fizz: "buzz" } } };
const context = createContext(initial);
export default context;
//

//snippet from Consumer.js
import context from "path/to/context";
const consumedContext = useContext(context);

/* 
changes context to 
{ foo: "bar" }  (plus update functions)
*/
consumedContext.removeFromContext(["fizz"]);

//

ContextReducerProvider API

Reducers are a familiar pattern used in state management libraries like Redux. Using a reducer can simplify and organize the logic of context updates.

This component uses immer under the hood, allowing users to mutate the previous state directly instead of the traditional React pattern of having to return a new state object.

Example Usage

import { createContext, useContext } from "react";
import { ContextReducerProvider } from "react-context-helper";

const context = createContext({});

const Consumer = () => {
  const { message, dispatch } = useContext(context);

  /* changes context to { message: "hello context!", fizz: "buzz"} (plus dispatch function) */
  dispatch({ type: "update", payload: { message: "hello context" } });
  dispatch({ type: "remove", payload: ["foo"] });

  // output: hello context!
  return <div>{message}</div>;
};

//takes a draft parameter (the current context) and the dispatched action
const reducer = (draft, action) => {
  switch (action.type) {
    case "update":
      Object.assign(draft, action.payload);
      break;
    case "remove":
      action.payload.forEach((key) => {
        delete draft[key];
      });
      break;
    default:
      break;
  }
};

const App = () => {
  return (
    <ContextReducerProvider
      contextObj={context}
      value={{ message: "hello world", foo: "bar", fizz: "buzz" }}
      reducer={reducer}
    >
      <Consumer />
    </ContextReducerProvider>
  );
};

Props

  • value: Object
    Identical to the value prop for any Context.Provider, except for the (current) requirement that it be a regular object.

  • contextObj: Context
    The React Context that the consumer will be consuming.

  • reducer: Function
    A reducer function to update the context based on dispatched actions. Accepts two arguments, draft and action. This is identical to the reducer passed to useReducer except that by using immer under the hood, it allows you to mutate draft directly, simplifying updates considerably.

    Example:

//snippet from Context.js
const initial = { foo: "bar", fizz: { buzz: { fizz: "buzz" } } };
const context = createContext(initial);
export default context;
//

//snippet from App.js
import context from "path/to/context";

const reducer = (draft, action) => {
  switch (action.type) {
    case "update":
      Object.assign(draft, action.payload);
      break;
    case "remove":
      action.payload.forEach((key) => {
        delete draft[key];
      });
      break;
    default:
      break;
  }
};

const App = () => {
  return (
    <ContextReducerProvider
      contextObj={context}
      value={initialValue}
      reducer={reducer}
    >
      <ChildWithConsumers />
    </ContextReducerProvider>
  );
};
//

Dispatch function added to the context object

The context value is assumed to be a regular object. An upcoming version will work for arrays and primitives as well.

The context provided by ContextReducerProvider includes a dispatch function which allows you to dispatch actions to your reducer in order to change your context as necessary:

dispatch(action)

Parameters:

  • draft: Object
    the current context (internally stored in state)
  • action: Object
    the action that was dispatched by dispatch

Return value:

  • void
//snippet from Context.js
const initial = { foo: "bar", fizz: "buzz" };
const context = createContext(initial);
export default context;
//

//snippet from App.js
const reducer = (draft, action) => {
  switch (action.type) {
    case "update":
      Object.assign(draft, action.payload);
      break;
    case "remove":
      action.payload.forEach((key) => {
        delete draft[key];
      });
      break;
    default:
      break;
  }
};
const App = () => {
  return;
  <ContextReducerProvider
    value={{ foo: "bar", fizz: "buzz" }}
    contextObj={context}
    reducer={reducer}
  >
    <Consumer />
  </ContextReducerProvider>;
};
//

//snippet from Consumer.js
import context from "path/to/context";

...

const { dispatch } = useContext(context);

/*
changes context to
{ fizz: "buzz"} (plus dispatch function) */
dispatch({ type: "remove", payload: ["foo"] });

//

useMemoConsumer API

Example Usage

In the following snippet, Consumer is unoptimized and will be rerendered every time fizz is updated, even though it does not use fizz.

import { createContext, useContext } from "react";
import { ContextProvider } from "react-context-helper";

const context = createContext({});

const Consumer = () => {
  const { foo } = useContext(context);
  return <div>{foo}</div>;
};

const App = () => {
  return (
    <ContextProvider contextObj={context} value={{ foo: "bar", fizz: "buzz" }}>
      <Consumer />
    </ContextProvider>
  );
};

The follow snippet prevents this rerender with useMemoConsumer. The recipe to optimize components is simple:

  1. Convert the consuming component to a pure component, meaning it takes the desired context properties as props and will always render the same way when given the same props.

  2. Use the hook useMemoConsumer to both memoize the consumer and automatically pass it only the props it needs from the context.

import { createContext, useContext } from "react";
import { ContextProvider, useMemoConsumer } from "react-context-helper";

const context = createContext({});

//converted to pure component
const Consumer = ({ foo }) => {
  return <div>{foo}</div>;
};

const App = () => {
  const [MemoizedConsumer, contextProps] = useMemoConsumer(Consumer, context, [
    "foo",
  ]);
  return (
    <ContextProvider contextObj={context} value={{ foo: "bar", fizz: "buzz" }}>
      <MemoizedPureConsumer {...contextProps} />
    </ContextProvider>
  );
};

Any non-context props can be added to the new memoized consumer as well:

//converted to pure component
const Consumer = ({ foo, nonContextProp }) => {
  return <div className="nonContextProp">{foo}</div>;
};

const App = () => {
  const [MemoizedConsumer, contextProps] = useMemoConsumer(Consumer, context, [
    "foo",
  ]);
  return (
    <ContextProvider contextObj={context} value={{ foo: "bar", fizz: "buzz" }}>
      <MemoizedPureConsumer {...contextProps} nonContextProp="myClassname" />
    </ContextProvider>
  );
};

Parameters

  • consumer: React.Component
    The consumer you are optimizing, in pure form.

  • contextObj: Context
    The React Context that the consumer will be consuming.

  • consumedProps: Array
    The keys of the properties in the context object that the consumer requires. E.g., if the context shape is:

    { foo: "bar", fizz: "buzz", bar: "foo" }

    and the consumer only uses foo and bar, then consumedProps would be ["foo", "bar"]

Return value:

  • Array
    An array with the optimized consumer at index 0 and the props object containing only the desired context properties at index 1.