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-super-context

v1.1.0-rc6

Published

A tiny wrapper library around the [React Context API](https://reactjs.org/docs/context.html) that removes a lot of boilerplate required to create and consume contexts.

Downloads

92

Readme

react-super-context

A tiny wrapper library around the React Context API that removes a lot of boilerplate required to create and consume contexts.

Features

  • Create contexts with no boilerplate
  • No more nested context providers
  • Throws an error when consuming a context that has no provider instead of failing silently
  • Built with TypeScript
  • Small bundle size

Installation

npm i react-super-context

Before and After

Before

// when using TypeScript, you must define an interface for the context's value to get proper type hints
interface CounterContextModel {
  count: number;
  increment: () => void;
  decrement: () => void;
}

// createContext expects a default value that is used if there are no providers for the context
const CounterContext = createContext<CounterContextModel>({
  count: 0,
  increment: () => {},
  decrement: () => {},
});

// we export a provider component that is responsible for the context's states 
export const CounterContextProvider = ({ children }: PropsWithChildren<{}>) => {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(Math.max(0, count - 1));

  return (
    <CounterContext.Provider value={{ count, increment, decrement }}>
      {children}
    </CounterContext.Provider>
  );
};

// we also export a hook that can be used to consume the context in our components
export const useCounter = () => useContext(CounterContext);

After

// createSuperContext returns a custom provider and a hook for consumption
const [CounterContext, useCounter] = createSuperContext(() => {
  // the state logic is the same as before
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(Math.max(0, count - 1));

  // we now simply have to return the context's value
  // when using TypeScript, the types are inferred and the useCounter hook will have proper type hints
  return { count, increment, decrement };
});

export { CounterContext, useCounter };

Before

const App = () => {
  return (
    <CounterContextProvider>
      <MySecondContextProvider>
        <MyThirdContextProvider>
            <div>Your app with consumers comes here</div>
        </MyThirdContextProvider>
      </MySecondContextProvider>
    </CounterContextProvider>
  );
};

After

const App = () => (
  <SuperContext contexts={[CounterContext, MySecondContext, MyThirdContext]}>
    <div>Your app with consumers comes here</div>
  </SuperContext>
);

Examples

1. Simple example

1. Use the createSuperContext function to create your context. It takes a factory function that returns the context's value and returns a context object as well as a hook to consume the state.

// CounterContext.ts
const [CounterContext, useCounter] = createSuperContext(() => {
  const [count, setCount] = useState(0);
  return {count, setCount};
});

export { CounterContext, useCounter };

2. To create a provider for the context, add the SuperContext component in your app and pass it the CounterContext created by the createSuperContext call.

// App.tsx
const App = () => (
  <SuperContext contexts={[CounterContext]}>
    <CountDisplay/>
    <CounterButton/>
  </SuperContext>
);

3. Consume the context in your components using the useCounter hook.

// CountDisplay.tsx
const CountDisplay = () => {
  const { count } = useCounter();
  return <div>{count}</div>;
};

// CounterButton.tsx 
const CounterButton = () => {
  const { count, setCount } = useCounter();
  return <button onClick={() => setCount(count + 1)}>+1</button>;
};

2. Use multiple contexts

1. Create a second context that uses useCounter.

// EvenOrOddContext.ts
const [EvenOrOddContext, useEvenOrOdd] = createSuperContext(() => {
  const { count } = useCounter();
  return count % 2 === 0 ? "even" : "odd";
});

export { EvenOrOddContext, useEvenOrOdd };

2. Remember to add it to the contexts lists. The order of the contexts matters.

// App.tsx
const App = () => (
  <SuperContext contexts={[CounterContext, EvenOrOddContext]}>
    <CountDisplay/>
    <CounterButton/>
  </SuperContext>
);

EvenOrOddContext depends on CounterContext so if they were given the other way around (contexts={[EvenOrOddContext, CounterContext]}), then the useCounter call in EvenOrOddContext.ts will throw an error.

3. Consume the new context.

// CountDisplay.tsx
export const CountDisplay = () => {
  const { count } = useCounter();
  const evenOrOdd = useEvenOrOdd();

  return <div>{count} ({evenOrOdd})</div>;
};

3. Use hooks as you normally would

const [Logging] = createSuperContext(() => {
  const { count } = useCounter();
  const evenOrOdd = useEvenOrOdd();

  useEffect(() => {
    console.log(`The current count is ${count} which is ${evenOrOdd}`);
  }, [count, evenOrOdd]);
});

export default Logging;

Remember to always add your context objects to the SuperContext component.

4. Passing props

1. Create a super context with the desired props.

// CounterContext.ts
interface CounterContextProps {
  initial: number;
}

const [CounterContext, useCounter] = createSuperContext(({ initial }: CounterContextProps) => {
  const [count, setCount] = useState(initial);
  return { count, setCount };
});

export { CounterContext, useCounter };

2. CounterContext is a function that you can pass the props to.

// App.tsx
const App = () => (
  <SuperContext contexts={[CounterContext({ initial: 10 })]}>
    <CountDisplay/>
    <CounterButton/>
  </SuperContext>
);

5. TypeScript

In all the examples above, TypeScript is able to infer the types of both the context's value (the value returned by the factory function and by the generated hook) and the contexts' props.

const CountDisplay = () => {
  const { count } = useCounter(); // inferred type: { count: number, increment: () => void, decrement: () => void }
  const evenOrOdd = useEvenOrOdd(); // inferred type: "even" | "odd"

  return <div>{count} ({evenOrOdd})</div>;
};

However, you can also define types explicitly:

1. Type given explicitly in createSuperContext call.

// CounterContext.ts
interface CounterContextModel {
    count: number;
    increment: () => void;
    decrement: () => void;
}

const [CounterContext, useCounter] = createSuperContext<CounterContextModel>(() => {
  const [count, setCount] = useState(0);
  const increment = () => setCount(count + 1);
  const decrement = () => setCount(Math.max(0, count - 1));

  return { count, increment, decrement };
});

2. Type inferred when consuming the context.

const CountDisplay = () => {
  const { count } = useCounter(); // inferred type: CounterContext
  return <div>{count}</div>;
};

6. Using props with Typescript

The simplest approach is to define the prop type in the argument for the factory function.

interface CounterContextProps {
  initial: number;
}

const [CounterContext, useCounter] = createSuperContext(({ initial }: CounterContextProps) => {
  const [count, setCount] = useState(initial);
  return { count, setCount };
});

If you have defined the context's value type explicitly, you must pass the prop type as the second generic argument (at least until TypeScript gets support for partial type argument inference).

const [CounterContext, useCounter] = createSuperContext<CounterContext, CounterContextProps>(({initial}) => {
  const [count, setCount] = useState(initial);
  return { count, setCount };
});

7. Options

The createSuperContext function takes an optional object as the second argument, allowing you to specify a number of options.

const [CounterContext, useCounter] = createSuperContext(
    () => {
        const [count, setCount] = useState(0);
        return { count, setCount };
    },
    {
        displayName: "MyCounterContext",
        testValue: { count: 0, setCount: () => {} },
    }
);

displayName will be the name of the context provider component in error messages. The testValue is the value returned by the useCounter hook in a test environment. The library will by default check if NODE_ENV === "test" to determine if it is in a test environment, but this can be overridden with the testEnvironment option.

If you use many of the same options on all context provided by a SuperContext, you can use the defaultOptions prop to set defaults:

const App = () => (
    <SuperContext
        contexts={[CounterContext, EvenOrOddContext]}
        defaultOptions={{displayName: "MyContext"}}
    >...</SuperContext>
);

In the example above, both the CounterContext and the EvenOrOddContext provider components will be displayed as "MyContext" in error messages.

More examples

See more examples here.