React Grimoire
  • ⁉️Introduction
  • 🧑‍🎓🧑🎓 React Fundamentals
    • The Basic javascript "Hello world"
    • Basic view on React core API's
    • JSX
    • React Props and styling
    • Components
    • Handling Events With React
    • Form
    • Array and Lists
    • Class Component State
    • Class Component Life Cycle
    • PropTypes
  • 🪝React Hooks
    • Intro to hooks
    • useState
    • useEffect
    • useRef
    • useContext
    • useMemo
    • useCallback
    • useReducer
    • useLayoutEffect
    • useImperativeHandle
    • Hook flow
    • Custom hooks
      • useDebugValue
    • React 18 hooks
  • 📓React Guidelines
    • React Components Composition Guidelines
    • React hooks guidelines
    • The use of Memoization
    • Lifting State Up
  • 🔭Concepts
    • Advanced Patterns
      • Compound Components Pattern
      • Control Props Pattern
      • Props Getters Pattern
      • Custom hook pattern
      • Context Module
      • State Reducer
    • React Under the hood
      • 🏁What is "Rendering"?
      • 🏁React Lifecycle
      • 🏁Reconciliation & Virtual DOM in React
      • 🏁Fiber
    • ⏰Concepts to always have in mind
  • 🧩React ecosystem
    • Forms Tools
      • React Hook Form VS Formik
    • TypeScript
      • 🏁Conditional React props with TypeScript
    • 🏁Build tool choice for MVP projects
    • A CSS methodology comparison
      • 🏁Post CSS processor :Sass
      • 🏁CSS in js :Styled-components
      • 🏁Utility Classes: Tailwind
  • ⁉️Testing
    • In Progress
  • 🎭Performance
    • in Progress
  • 🚀Deployment
    • In Progress
  • 🖼️Design system
    • 🏁What's a design system anyway ?​?
  • 🔗Us-full links
    • Typescript and React basic tutorial
    • React-philosophies
    • React new doc
Powered by GitBook
On this page

Was this helpful?

Edit on GitHub
  1. Concepts
  2. Advanced Patterns

Context Module

You can use the Context Module Functions Pattern to encapsulate a complex set of state changes in a utility function that can be tree-shaken and loaded lazily.

Let's take a look at an example of a simple context and a reducer combo:

const CounterContext = React.createContext()

function CounterProvider({step = 1, initialCount = 0, ...props}) {
  const [state, dispatch] = React.useReducer(
    (state, action) => {
      const change = action.step ?? step
      switch (action.type) {
        case 'increment': {
          return {...state, count: state.count + change}
        }
        case 'decrement': {
          return {...state, count: state.count - change}
        }
        default: {
          throw new Error(`Unhandled action type: ${action.type}`)
        }
      }
    },
    {count: initialCount},
  )

  const value = [state, dispatch]
  return <CounterContext.Provider value={value} {...props} />
}

function useCounter() {
  const context = React.useContext(CounterContext)
  if (context === undefined) {
    throw new Error(`useCounter must be used within a CounterProvider`)
  }
  return context
}

export {CounterProvider, useCounter}
import {useCounter} from 'context/counter'

function Counter() {
  const [state, dispatch] = useCounter()
  const increment = () => dispatch({type: 'increment'})
  const decrement = () => dispatch({type: 'decrement'})
  return (
    <div>
      <div>Current Count: {state.count}</div>
      <button onClick={decrement}>-</button>
      <button onClick={increment}>+</button>
    </div>
  )
}
import {CounterProvider} from 'context/counter'

function App() {
  return (
    <CounterProvider>
      <Counter />
    </CounterProvider>
  )
}

I want to focus in on the user of our reducer (the Counter component). Notice that they have to create their own increment and decrement functions which call dispatch. I don't think that's a super great API. It becomes even more of an annoyance when you have a sequence of dispatch functions that need to be called (like you'll see in our exercise).x

The first inclination is to create "helper" functions and include them in the context. Let's do that. You'll notice that we have to put it in React.useCallback so we can list our "helper" functions in dependency lists):

const increment = React.useCallback(
  () => dispatch({type: 'increment'}),
  [dispatch],
)
const decrement = React.useCallback(
  () => dispatch({type: 'decrement'}),
  [dispatch],
)
const value = {state, increment, decrement}
return <CounterContext.Provider value={value} {...props} />

// now users can consume it like this:

const {state, increment, decrement} = useCounter()

Helper methods are object junk that we need to recreate and compare for no purpose other than superficially nicer looking syntax.

Dan suggests (and what Facebook does) the use of importable "helpers" that accept dispatch. Let's take a look at how that might work:

const CounterContext = React.createContext()

function CounterProvider({step = 1, initialCount = 0, ...props}) {
  const [state, dispatch] = React.useReducer(
    (state, action) => {
      const change = action.step ?? step
      switch (action.type) {
        case 'increment': {
          return {...state, count: state.count + change}
        }
        case 'decrement': {
          return {...state, count: state.count - change}
        }
        default: {
          throw new Error(`Unhandled action type: ${action.type}`)
        }
      }
    },
    {count: initialCount},
  )

  const value = [state, dispatch]

  return <CounterContext.Provider value={value} {...props} />
}

function useCounter() {
  const context = React.useContext(CounterContext)
  if (context === undefined) {
    throw new Error(`useCounter must be used within a CounterProvider`)
  }
  return context
}

const increment = dispatch => dispatch({type: 'increment'})
const decrement = dispatch => dispatch({type: 'decrement'})

export {CounterProvider, useCounter, increment, decrement}
import {useCounter, increment, decrement} from 'context/counter'

function Counter() {
  const [state, dispatch] = useCounter()
  return (
    <div>
      <div>Current Count: {state.count}</div>
      <button onClick={() => decrement(dispatch)}>-</button>
      <button onClick={() => increment(dispatch)}>+</button>
    </div>
  )
}

References and articles :

PreviousCustom hook patternNextState Reducer

Last updated 3 years ago

Was this helpful?

This isn't a bad solution necessarily. But said:

This may look like overkill, and it is. However, in some situations this pattern can not only help you reduce duplication, but it also and helps you avoid mistakes in dependency lists.

🔭
DanAbramov
helps improve performance
Context Module Pattern in ReactMedium
Advanced React Patterns NoteMedium
Logo
Logo