React Context + State

Last update: Jun 24, 2022

constate logo

Constate

NPM version NPM downloads Size Dependencies Build Status Coverage Status

Write local state using React Hooks and lift it up to React Context only when needed with minimum effort.


🕹 CodeSandbox demos 🕹
Counter I18n Theming TypeScript Wizard Form

Basic example

import React, { useState } from "react";
import constate from "constate";

// 1️⃣ Create a custom hook as usual
function useCounter() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(prevCount => prevCount + 1);
  return { count, increment };
}

// 2️⃣ Wrap your hook with the constate factory
const [CounterProvider, useCounterContext] = constate(useCounter);

function Button() {
  // 3️⃣ Use context instead of custom hook
  const { increment } = useCounterContext();
  return <button onClick={increment}>+</button>;
}

function Count() {
  // 4️⃣ Use context in other components
  const { count } = useCounterContext();
  return <span>{count}</span>;
}

function App() {
  // 5️⃣ Wrap your components with Provider
  return (
    <CounterProvider>
      <Count />
      <Button />
    </CounterProvider>
  );
}

Learn more

Advanced example

import React, { useState, useCallback } from "react";
import constate from "constate";

// 1️⃣ Create a custom hook that receives props
function useCounter({ initialCount = 0 }) {
  const [count, setCount] = useState(initialCount);
  // 2️⃣ Wrap your updaters with useCallback or use dispatch from useReducer
  const increment = useCallback(() => setCount(prev => prev + 1), []);
  return { count, increment };
}

// 3️⃣ Wrap your hook with the constate factory splitting the values
const [CounterProvider, useCount, useIncrement] = constate(
  useCounter,
  value => value.count, // becomes useCount
  value => value.increment // becomes useIncrement
);

function Button() {
  // 4️⃣ Use the updater context that will never trigger a re-render
  const increment = useIncrement();
  return <button onClick={increment}>+</button>;
}

function Count() {
  // 5️⃣ Use the state context in other components
  const count = useCount();
  return <span>{count}</span>;
}

function App() {
  // 6️⃣ Wrap your components with Provider passing props to your hook
  return (
    <CounterProvider initialCount={10}>
      <Count />
      <Button />
    </CounterProvider>
  );
}

Learn more

Installation

npm:

npm i constate

Yarn:

yarn add constate

API

constate(useValue[, ...selectors])

Constate exports a single factory method. As parameters, it receives useValue and optional selector functions. It returns a tuple of [Provider, ...hooks].

useValue

It's any custom hook:

import { useState } from "react";
import constate from "constate";

const [CountProvider, useCountContext] = constate(() => {
  const [count] = useState(0);
  return count;
});

You can receive props in the custom hook function. They will be populated with <Provider />:

const [CountProvider, useCountContext] = constate(({ initialCount = 0 }) => {
  const [count] = useState(initialCount);
  return count;
});

function App() {
  return (
    <CountProvider initialCount={10}>
      ...
    </CountProvider>
  );
}

The API of the containerized hook returns the same value(s) as the original, as long as it is a descendant of the Provider:

function Count() {
  const count = useCountContext();
  console.log(count); // 10
}

selectors

Optionally, you can pass in one or more functions to split the custom hook value into multiple React Contexts. This is useful so you can avoid unnecessary re-renders on components that only depend on a part of the state.

A selector function receives the value returned by useValue and returns the value that will be held by that particular Context.

import React, { useState, useCallback } from "react";
import constate from "constate";

function useCounter() {
  const [count, setCount] = useState(0);
  // increment's reference identity will never change
  const increment = useCallback(() => setCount(prev => prev + 1), []);
  return { count, increment };
}

const [Provider, useCount, useIncrement] = constate(
  useCounter,
  value => value.count, // becomes useCount
  value => value.increment // becomes useIncrement
);

function Button() {
  // since increment never changes, this will never trigger a re-render
  const increment = useIncrement();
  return <button onClick={increment}>+</button>;
}

function Count() {
  const count = useCount();
  return <span>{count}</span>;
}

Contributing

If you find a bug, please create an issue providing instructions to reproduce it. It's always very appreciable if you find the time to fix it. In this case, please submit a PR.

If you're a beginner, it'll be a pleasure to help you contribute. You can start by reading the beginner's guide to contributing to a GitHub project.

When working on this codebase, please use yarn. Run yarn examples to run examples.

License

MIT © Diego Haz

GitHub

https://github.com/diegohaz/constate
Comments
  • 1. Fallback to local state when there's no Provider

    Thanks so much for constate - wonderful, indeed!

    I'm using it in a rather large project and finding that it is tedious to wrap every test with a Provider. React's use of a default context value in createContext helps with this normally because then useContext will get the default, even if no provider is in the tree. Alas, you can't do that with constate because there's no way use the hook outside of a component.

    I'd love to get your thoughts on this... What I'm thinking of is something like this (admittedly big) api change:

    // OLD
    const counter = useContext(CounterContainer.Context);
    
    // NEW
    const counter = CounterContainer.use();
    

    The use() function does the useContext subscription, but also knows how to call the underlying hook instead if the context is empty. This makes writing tests really easy - just like React's createContext behavior when no provider is present.

    There are lots of (mostly unfortunate) issues with this proposal, tho:

    1. Since we use undefined in the context to signify the no-provider case, container hooks can never return undefined (seems pretty minor?). Easy to guard for that case.

    2. Since use() has to call the hook when no provider is given, container hooks cannot have props anymore (seems pretty major).

    3. It is a bit nasty to conditionally call the hook (use will call it if no provider is present, otherwise it won't). But this might not be a problem ITRW because you're either running real code (with a provider) or you're running tests (without a provider) and the render tree should be consistent across renders in each of those cases. React will complain if you get this wrong (like conditionally adding/removing providers, which seems super unlikely).

    So it ends up like something like this:

    function createContainer<V>(
      useValue: () => V,
      createMemoInputs?: (value: V) => any[]
    ) {
      const Context = createContext<V | undefined>(undefined);
    
      const Provider = (props: { children?: React.ReactNode }) => {
        const value = useValue();
        if (value === undefined) {
          throw new Error('Container hooks must return a value');
        }
    
        // createMemoInputs won't change between renders
        const memoizedValue = createMemoInputs
          ? React.useMemo(() => value, createMemoInputs(value))
          : value;
        return (
          <Context.Provider value={memoizedValue}>
            {props.children}
          </Context.Provider>
        );
      };
    
      const use = () => {
        let value = useContext(Context);
        if (value === undefined) {
          warnNoProvider();
          value = useValue();
        }
        return value;
      };
    
      return {
        use,
        Provider
      };
    }
    
    Reviewed by ehahn9 at 2019-04-06 16:58
  • 2. Reqeust: with(apply)Provider(Container) HOC

    Hello there, I think we still need a helper HOC for applying the container provider named withProvider/withContainer/withContainerProvider or whatever. Otherwise, we always have to modify some component's parent to add such codes <Container.Provider>....</Container.Provider> and when we decide to remove a context we have to modify its parent again and again. I have a request that adding the HOC helper:

    import React, { useState, useContext, useEffect } from "react";
    import createContaine, { withProvider }r from "constate";
    import {userCounter, MainCounter} from '../containers/counter'
    import { Button, Count } from '../components'
    
    // we wish use context inside the DynamicImportedRoutePage(current componet),
    // not only its descendants
    function DynamicImportedRoutePage() {
       // we may use count context here
      const { increment } = useContext(MainCounter.Context);
    
        useEffect(
           () => {
               increment()
           },
           []
         )
    
      return (
        <>
          <Count />
          <Button />
        </>
      );
    }
    
    // Use the HOC
    export default withProvider(MainCounter)(DynamicImportedRoutePage)
    

    When we decide to refactor the DynamicImportedRoutePage, just remove/change the HOC and update the function body

    Reviewed by zheeeng at 2018-12-15 01:05
  • 3. Add Context Typescript Example

    Add typescript example of using context adapted with typings from my previous example before official typescript support.

    For typescript example support I changed examples/index.js to a .tsx extension. JS component examples still work

    To ease future additions of examples I created a simple array of tuples that can be used to map a route to a component example.

    // [0] Name and Route
    // [1] Component
    const routeMap: [string, React.ReactNode][] = [
      ['Counter', Counter],
      ['ContextCounter', ContextCounter]
    ];
    

    Also, i placed the list and Example renderings in a flex container to better separate the example from the list.

    I'm interested to help more on examples and making this look and function better. I'm thinking along the lines of something much simpler than, but similar to Storybook?

    @diegohaz feedback? Also, am I using ComposableContainer typing properly?

    capture

    Reviewed by codyaverett at 2018-08-24 07:17
  • 4. Side effects / async actions

    I'm thinking on a way to handle side effects and/or async actions on constate. This is how I'm currently planning to use it:

    const effects = {
      createPost: payload => async ({ state, setState, props }) => {
        try {
          const data = await api.post('/posts', payload)
          setState({ data })
        } catch (error) {
          setState({ error })
        }
      }
    }
    

    Possible names:

    • asyncActions
    • effects
    • thunks
    • methods
    • (suggestions are welcome)
    Reviewed by diegohaz at 2018-03-31 19:32
  • 5. Types are not inferred properly

    My hook has properly inferred types, see here, this is the types that my hook has as seen by the Typescript Service.

    My hook: image

    But after passing through constate, the new Provider and Hook that are created have no inferred types, so I'm not getting any TS help/completions anywhere.

    Constate's hook: image

    Return from constate hook (no types): image

    Reviewed by timkindberg at 2021-07-02 01:36
  • 6. Accept generics in hook

    Hey!

    Actually I am using const list = useList()

    function useListContext() {
      const [data, setData] = useState([])
    
     
      // ommited
    
      return {
        data,
        feedData,
      }
    }
    
    export const [
      ListProvider,
      useList, // I want to pass the generics to this hook, to use in setData state
    ] = constate(useListContext)
    

    There are any support like these in your roadmap?

    Thanks

    Reviewed by yuritoledo at 2020-06-09 21:45
  • 7. Anti-pattern question

    Is it an anti-pattern to cache the setContextState function for subsequent use by a non-React-Context component?

    For example, this approach appears to work in practice:

    
    // Non-context module
    
    export const onMount = ({ setContextState }) => {
      cachedSetContextState = setContextState;
    };
    

    Which can be registered with a <Provider>:

    <Provider onMount={onMount} />
    

    So that later on in the app the cached context state can be accessed to update the context state by a component that isn't otherwise bound into the React context:

    
    // In the same non-context module as above
    // Has access to the cached `cachedSetContextState` function
    cachedSetContextState('context', { foo: 'bar' });
    

    Is this a bad idea?

    What I think I trying to achieve is something like channels in redux-saga, i.e. to be able to mutate the state from a non-GUI source.

    Reviewed by 0x6e6562 at 2018-10-10 11:18
  • 8. How can i use overloading with constate?

    function useNavigation(): NavigationRoutes
    function useNavigation(route: keyof NavigationRoutes): NavigationRoute
    function useNavigation(route?: keyof NavigationRoutes): NavigationRoute | NavigationRoutes {}
    
    export const [NavigationProvider, useNavigationContext] = constate(
      useNavigation
    )
    
    Reviewed by polRk at 2020-12-08 21:41
  • 9. Is it possible to have a ref to the Provider?

    Is it possible to get a ref to the Provider and then use that ref in the hook?

    For example, would it be possible to do something like this?:

    function useValueWithRef({ initialValue }, ref) {
       const [value, setValue] = useState(initialValue);
       
       useImperativeHandle(ref, () => ({ setValue }));
    
       return { setValue, value };
    }
    
    const [ValueProvider, useValueContext] = constate(useValueWithRef);
    
    function App() {
       const ref = useRef();
    
       useEffect(() => {
          setTimeout(() => {
             ref.current.setValue('b');
          }, 2000);
       }, []);
    
       return (
          <ValueProvider ref={ref} initialValue="a">
            {/* child components... */}
          </ValueProvider>
       );
    }
    
    Reviewed by awkpagong at 2020-10-19 00:33
  • 10. Better Typescript types

    I'm learning TS and spent a long time figuring out how to type Constate properly, turns out it wasn't possible in TS 3, I needed TS 4:

    declare module 'constate' {
      function constate<
        StoreProps,
        StoreValue,
        SplitValue extends ((p: StoreValue) => any),
        SplitValues extends ((p: StoreValue) => any)[],
      >(
        useValue: (p: StoreProps) => StoreValue,
        splitValue: SplitValue,
        ...splitValues: SplitValues
      ): [
        React.FC<StoreProps>,
        () => ReturnType<SplitValue>,
        ...(SplitValues extends infer A
        ? {
          [K in keyof A]: () => (A[K] extends (...args: any) => infer R ? R : never);
        }
        : never),
      ];
    
      function constate<
        StoreProps,
        StoreValue,
      >(useValue: (p: StoreProps) => StoreValue): [
        React.FC<StoreProps>,
        () => StoreValue,
      ];
    
      export default constate;
    }
    

    Since TS 4 is still in beta, this shouldn't be included in Constate yet, but I thought I'd share. Let me know if there's anything that can be improved!

    Reviewed by Linksku at 2020-08-18 19:46
  • 11. add `devtoolsName` prop to Provider to set redux-devtools-extension's instance name

    For cases where constate default Provider/from createContext one is used in parts of an app instead of globally, we can customize the instance name instead of generated Instance 1...N.

    Not sure about the prop name, it can be changed to more meaningful one.

    Reviewed by panjiesw at 2018-12-04 18:51
  • 12. How to create selectors in order to listen on multiple changes in context's state?

    First of all, I should say thank you for your useful module. today I created a context's state and I tried to use a selector function that gets props names and returns a new state but it can't prevent unnecessary component renders. So is there any way to create such selector functions?

    const init = { name: 'John', date: null }
    
    const withReducerState = () => {
       const [state, dispatch] = useReducer(reducer, init)
       return {state, dispatch}
    }
    
    const [Provider, useContext, useName, useDate, useSelector] = constate(
         withReducerState,
         value => ({ state: value.state, dispatch: value.dispatch }),
         value => value.state.name,
         value => value.state.date,
         // Create a selector function that get an array of state props --- ['name', 'date']
         value => {
             return (selectors) => {
                 console.log("$$$$$$$$$$$$$$$ selectors $$$$$$$$$$$", selectors)
                 let state = {}
                 for(let item of selectors) {
                    state[item] = value.state[item]
                 }
                 console.log("$$$$$$$$$$$$$$$ selected state $$$$$$$$$$$", state)
                 return state;
             }
         }
    )
    
    // In my Component I want to use the selector hook.
    const MyComponent = () => {
        const {name, date} = useSelector()(['name', 'date'])
    
    Reviewed by saeidalidadi at 2021-10-07 15:55
  • 13. Mock initial state

    Hey!

    Let's say I have a provider that provides a CPF inside it state value. Then I use it in my component like this const { cpf } = useStateSignIn()

    In my integration test (with Jest and Testing Library) I need to mock this cpf I got from my provider.

    How can I do this?

    Reviewed by luiznasciment0 at 2021-05-07 03:08
  • 14. Added `Provider.FromValue`, to enable mocking in Storybook and Unit Tests

    I have started using constate to separate my data-fetching logic from my UI, and it's perfect for the job! Thank you.

    However, in Storybook and in Unit tests, I'd like to be able to manually provide (mock) values to the context provider. So, in this PR, I've exposed a property on the Provider called Provider.FromValue, which is just a Provider that ignores the hook data and uses the provided data instead.

    Storybook example:

    const [CounterProvider, useCounterContext] = constate(useCounter);
    export const withMockedValues = <>
      <CounterProvider.FromValue value={{ count: 0, increment: action('increment') }}>
        <Increment />
        <Count />
      </CounterProvider.FromValue>
    </>;
    

    React Testing Library example:

    test("increment gets called", () => {
      const mockCounter = { count: 0, increment: jest.fn() };
      const MockProvider: React.FC = ({ children }) => (
        <CounterProvider.FromValue value={mockCounter}>
          {children}
        </CounterProvider.FromValue>
      );
      const { getByText } = render(
        <div>
          <Increment />
          <Count />
        </div>,
        { wrapper: MockProvider }
      );
      fireEvent.click(getByText("Increment"));
      expect(getByText("10")).toBeDefined();
      expect(mockCounter.increment).toHaveBeenCalledTimes(1);
    });
    

    I'm open to feedback, especially regarding naming things, so please let me know!

    TODO:

    • [x] Add FromValue feature
    • [x] Add unit tests
    • [ ] Add documentation
    Reviewed by scottrippey at 2021-01-06 17:05
  • 15. Possible to do lazy splitting at time of hook usage?

    Hi Diego! Thinking of using this in addition to or instead of zustand. One thing I like about zustand is that you can select from the state when you use the hook, like so:

    // If you don't pass anything to the hook it returns everything, and rerenders when anything changes
    const foo = useMyStore()
    
    // However, if you pass a selector fn, then it will only "watch" the selected state.
    // Good for perf optimizations.
    const foo = useMyStore(state => state.foo)
    
    // By default it's a referential equality check, but you can pass different equality fns.
    // So you could pass lodash's isEqual to do a deep equal comparison if you needed to.
    const someBigObj = useMyStore(state => state.someBigObj, _.isEqual)
    

    Do you think it's possible to accomplish this with constate? Like this?

    import React, { useState } from "react";
    import createStore from "zustand";
    
    // 1️⃣ Create a custom hook as usual
    function useCounter() {
      const [count, setCount] = useState(0);
      const increment = () => setCount(prevCount => prevCount + 1);
      return { count, increment };
    }
    
    // 2️⃣ Wrap your hook
    const [Provider, useCounterContext] = constate(useCounter)
    
    function Button() {
      // 3️⃣ Use store instead of custom hook, make use of selector api
      const increment = useCounterContext(s => s.increment);
      return <button onClick={increment}>+</button>;
    }
    
    function Count() {
      // 4️⃣ Use store in other components
      const count = useCounterContext(s => s.count);
      return <span>{count}</span>;
    }
    
    function App() {
      return (
        <Provider>
          <Count />
          <Button />
        </Provider>
      );
    }
    
    Reviewed by timkindberg at 2020-05-07 03:02
  • 16. Allow user to control console

    I currently intentionally have some providers as optional, so I often get console warnings from constate about the provider not being provided.

        if (isDev && value === NO_PROVIDER) {
          // eslint-disable-next-line no-console
          console.warn("[constate] Component not wrapped within a Provider.");
        }
    

    I would like to hide them for my use-case. I'd recommend the feature be "fixed" by allowing the user to add their own console object, then it would be up to the user to filter this specific message.

    import constate, { useConsole } from 'constate';
    import log from 'loglevel';
    
    const constateLogger= log.getLogger('constate');
    constateLogger.setLevel(log.levels.ERROR);
    useConsole(constateLogger)
    
    const [Provider, hook] = constate(.....);
    
    Reviewed by MattShrider at 2020-03-05 20:04
A simple and normalized React Provider generator using the React Context API

react-provider-factory A simple and normalized React Provider generator using th

Apr 2, 2022
React hook to communicate among browser context (tabs, windows, iframes)
React hook to communicate among browser context (tabs, windows, iframes)

react-window-communication-hook React hook to communicate among browsers contexts (windows, tabs, iframes). Example use case: When the user presses lo

Feb 8, 2022
React dismissable context and hook with layers (nesting) support
React dismissable context and hook with layers (nesting) support

react-dismissable-layers maintained by @voiceflow Context and hook to add support for nested, auto-dismissable layers. State can be globally controlle

Feb 4, 2022
Todo-App with React-Context

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Jan 2, 2022
React Parameter Context

React Parameter Context This library implements a pattern to declare global state for an application and configure how to initialize that state (ex vi

Mar 2, 2022
An experimental hook that lets you have multiple components using multiple synced states using no context provider

Resynced (experimental) ❤️ Motivation This is an experimental hook that lets you have multiple components using multiple synced states using no contex

May 9, 2021
Simple global state for React with Hooks, which just depends on React's useEffect and useState.

react-hooks-simple-global-state Simple global state for React with Hooks, which just depends on React's useEffect and useState. The idea here is simpl

May 29, 2022
The simple but very powerful and incredibly fast state management for React that is based on hooks

Hookstate The simple but very powerful and incredibly fast state management for React that is based on hooks. Why? • Docs / Samples • Demo application

Jun 16, 2022
React Hook for state management with profunctor lenses

Profunctor State Hook React Hook for state management with Profunctor Optics A simple and small (2KB!) approach to state management in React using fun

Mar 23, 2022
State management that tailored for react, it is simple, predictable, progressive and efficient.
State management that tailored for react, it is simple, predictable, progressive and efficient.

English | 简体中文 ⚡️ State management that tailored for react, it is simple, predictable, progressive and efficient. ?? Introduction Concent is an amazin

Jun 21, 2022
Vegetarian friendly state for React
Vegetarian friendly state for React

Vegetarian friendly state for React Easy Peasy is an abstraction of Redux, providing a reimagined API that focuses on developer experience. It allows

Jun 24, 2022
😎📋 React hooks for forms state and validation, less code more performant.

React hooks for forms state and validation, less code more performant. Features ?? Easy to use, just a React hook. ?? Manages complex form data withou

Jun 23, 2022
Tiny utility package for easily creating reusable implementations of React state provider patterns.

react-state-patterns Tiny utility package for easily creating reusable implementations of React state provider patterns. ?? react-state-patterns makes

Feb 4, 2022
State usage tracking with Proxies. Optimize re-renders for useState/useReducer, React Redux, Zustand and others.

React Tracked State usage tracking with Proxies. Optimize re-renders for useState/useReducer, React Redux, Zustand and others. Documentation site: htt

Jun 18, 2022
📄 React hook for managing forms and inputs state
📄 React hook for managing forms and inputs state

react-use-form-state ?? Table of Contents Motivation Getting Started Examples Basic Usage Initial State Global Handlers Advanced Input Options Custom

May 27, 2022
Predictable state container for React apps written using Hooks
Predictable state container for React apps written using Hooks

Redhooks is a tiny React utility library for holding a predictable state container in your React apps. Inspired by Redux, it reimplements the redux pa

Jan 3, 2022
Use immer to drive state with a React hooks

use-immer A hook to use immer as a React hook to manipulate state. Installation npm install immer use-immer API useImmer useImmer(initialState) is ver

Jun 21, 2022
useMedia React hook to track CSS media query state

use-media useMedia React sensor hook that tracks state of a CSS media query. Install You can install use-media with npm npm install --save use-media o

Jun 16, 2022
React Hook for managing state in URL query parameters with easy serialization.

useQueryParams A React Hook, HOC, and Render Props solution for managing state in URL query parameters with easy serialization. Works with React Route

Jun 22, 2022