Context

Context is designed to share data that can be considered “global” for a tree of React components

React

  • e.g. current user, theme, language, settings
  • props that need to be passed thru layers of components
  • parent to child communication
  • can be nested, closest value wins

How?

import React, { createContext, FC, useContext } from 'react'
import classes from './example.module.css'

const Box: FC<{ color: string; dashed?: boolean }> = ({
  color,
  dashed,
  children,
}) => (
  <div
    className={classes.box}
    style={{ borderColor: color, borderStyle: dashed ? 'dashed' : 'solid' }}
  >
    {children}
  </div>
)

const GreenDashedBox: FC = ({ children }) => (
  <Box color="#50fa7b" dashed>
    {children}
  </Box>
)

const Context = createContext<string>('default')
const ProvidesContext: FC<{ color: string }> = ({ color, children }) => (
  <Box color={color}>
    <pre>providing value: `{color}`</pre>
    <Context.Provider value={color}>{children}</Context.Provider>
  </Box>
)
const RendersContext: FC = () => {
  const value = useContext(Context)
  return <pre>using value: `{value}`</pre>
}

export default (
  <>
    <p>default value is used when there is no provider</p>
    <RendersContext />
    {/* default */}

    <ProvidesContext color="#ff5555">
      <RendersContext />
      {/* #ff5555 */}

      <GreenDashedBox>
        <p>value pierces component boundaries</p>
        <RendersContext />
        {/* #ff5555 */}

        <ProvidesContext color="#8be9fd">
          <p>providers can be nested</p>
          <p>closest value is used</p>
          <RendersContext />
          {/* #8be9fd */}
        </ProvidesContext>
      </GreenDashedBox>
    </ProvidesContext>
  </>
)

default value is used when there is no provider

using value: `default`
providing value: `#ff5555`
using value: `#ff5555`

value pierces component boundaries

using value: `#ff5555`
providing value: `#8be9fd`

providers can be nested

closest value is used

using value: `#8be9fd`

Dynamic Theme

import React, { createContext, FC, useContext, useState } from 'react'
import defaultClasses from './button.default.module.css'
import greenClasses from './button.green.module.css'
import redClasses from './button.red.module.css'

export type ThemeContextType = 'default' | 'red' | 'green'
export const ThemeContext = createContext<ThemeContextType>('default')

export const Button: FC = () => {
  const theme = useContext(ThemeContext)
  const classes = {
    default: defaultClasses,
    red: redClasses,
    green: greenClasses,
  }[theme]

  return (
    <button type="button" className={classes.button}>
      themed button
    </button>
  )
}

export const ThemeSwitcher: FC = ({ children }) => {
  const [theme, setTheme] = useState('default')

  return (
    <>
      <label>
        theme
        <select value={theme} onChange={(e) => setTheme(e.target.value)}>
          <option>default</option>
          <option>red</option>
          <option>green</option>
        </select>
      </label>
      <br />
      <ThemeContext.Provider value={theme as ThemeContextType}>
        {children}
      </ThemeContext.Provider>
    </>
  )
}

export default (
  <ThemeSwitcher>
    <Button />
  </ThemeSwitcher>
)