Back to all posts

Use createStateContext to share a useState hook

Despite so many folks touting React's useContext as a state management solution, it really doesn't have anything to do with state management at all.

React Context is about dependency injection. You provide a value to a wrapper component, and then any downstream component can use that value via a useContext hook.

Most of the time I use Context I also want to provide a way to update the value from the child components. I like the useState API, but it needs to be shareable

I've built a helper function called createStateContext that takes the same types as useState and returns a tuple of the Context and a hook to use it.

Consider a CheckboxGroup component, which needs to keep track of which checkboxes are checked.

const [CheckboxContext, useCheckboxContext] =
  createStateContext<string[]>()
 
// optional for better error messages
CheckboxContext.displayName = "CheckboxContext"
 
function CheckboxGroup({ children }) {
  const state = useState<string[]>([])
 
  return (
    <CheckboxContext.Provider value={state}>
      {children}
    </CheckboxContext.Provider>
  )
}

Any child component can use the useCheckboxContext hook to get a state and setState function that is shared with all its siblings.

const [checkboxes, setCheckboxes] = useCheckboxContext()
<CheckboxGroup>
  <Checkbox value="foo" />
  <Checkbox value="bar" />
  <Checkbox value="baz" />
</CheckboxGroup>

The hook

import React, { useContext } from "react"
 
export function createStateContext<T>() {
  const StateContext = React.createContext<
    | undefined
    | Readonly<[T, React.Dispatch<React.SetStateAction<T>>]>
  >(undefined)
 
  function useStateContext() {
    const tuple = useContext(StateContext)
 
    if (tuple === undefined) {
      throw new Error(
        `use${StateContext.displayName} must be used within a ${StateContext.displayName}Provider`,
      )
    }
 
    return tuple
  }
 
  return [StateContext, useStateContext] as const
}