🪝

お手軽用 React Context で State 管理のテンプレート

2022/02/25に公開

ミニマムだけど使いやすい(と思ってる)テンプレートです。
createContext 周りを書くのが地味にだるいので使っています。

DEMO: minimum-use-context-config - CodeSandbox

コード

import React, { createContext, useContext, useEffect, useState } from 'react'

type Config = {
  version: number
  showList: boolean
  showName: boolean
  mode: 'a' | 'b'
}

const defaultConfig: Config = {
  version: 3,
  showList: true,
  showName: false,
  mode: 'a',
} as const

const ConfigContext = createContext<[Config, (config: Config) => void]>([
  defaultConfig,
  () => {},
])

const migrate = (config: Config) => {
  // NOTE: if (config.version < 3) { // custom migration logic }
  return { ...defaultConfig, ...config, version: defaultConfig.version }
}

export const ConfigProvider: React.FC = ({ children }) => {
  const [config, setConfig] = useState<Config>(defaultConfig)

  useEffect(() => {
    if (config.version === defaultConfig.version) return
    setConfig(migrate(config))
  }, [config, setConfig])

  return (
    <ConfigContext.Provider value={[config, setConfig]}>
      {children}
    </ConfigContext.Provider>
  )
}

export const useConfig = () => useContext(ConfigContext)

使い方

function Page() {
  const [config, setConfig] = useConfig()

  return <div>{config.mode}</div>
}

function App() {
  return (
    <div className="App">
      <ConfigProvider>
        <Page />
      </ConfigProvider>
    </div>
  )
}

部分的な Config Hook に分割する

export const useSomeConfig = <Key extends keyof Config>(key: Key) => {
  const [config, setConfig] = useConfig()

  return [
    config[key],
    (v: Config[Key]) => setConfig({ ...config, [key]: v }),
  ] as const
}
export const useMode = () => useSomeConfig('mode')

const ModeRadios = () => {
  const [mode, setMode] = useMode()

  const handle = (e) => {
    setMode(e.target.value)
  }
  const radioProps = (value: 'a' | 'b'): React.HTMLProps<HTMLInputElement> => ({
    type: 'radio',
    onChange: handle,
    value,
    checked: value === mode,
    name: 'mode',
  })

  return (
    <div>
      <input id="modeA" {...radioProps('a')} />
      <label htmlFor="modeA">A</label>

      <input id="modeB" {...radioProps('b')} />
      <label htmlFor="modeB">B</label>
    </div>
  )
}

個別の設定をもう少しカスタムするパターン

export const useShowList = () => {
  const [showList, setShowList] = useSomeConfig('showList')
  const toggleConfig = () => setShowList(!showList)

  return { showList, setShowList, toggleConfig }
}

const ShowListOption = () => {
  const { showList, setShowList } = useShowList()

  return (
    <div>
      <input
        id="showListCheck"
        type="checkbox"
        checked={showList}
        onChange={(e) => {
          setShowList(e.target.checked)
        }}
      />
      <label htmlFor="showListCheck">ListVisible</label>
    </div>
  )
}

State の Storage を変える

import { useLocalStorage } from 'react-use'
// import { useSessionStorage } from 'react-use'

export const ConfigProvider: React.FC = ({ children }) => {
  const [config, setConfig] = useLocalStorage<Config>('config-name')
  // const [config, setConfig] = useSessionStorage<Config>('config-name')
}
GitHubで編集を提案

Discussion