LoginSignup
60
45

More than 3 years have passed since last update.

【React + Typescript】useContext の値を子コンポーネントから更新

Last updated at Posted at 2020-02-03

やること

React Hooks の useContext を使用すると、Provider 以下のどのコンポーネントからも値を参照できる。
子コンポーネントから Context の値を更新するには、カスタムフックを作成することで実現できる。

環境

  • CentOS7
  • typescript 3.7
  • React 16.12.0 (Next 9.2.0)

useContext について

React には Context という機能があり、
Provider 以下のいろんなコンポーネントから値を共有出来る仕組み。

いろんなコンポーネントが用いる設定(例: ロケール設定、カラーテーマ等)を管理するのに使用する事が多い。
useContextは React Hooks で Context を扱うメソッド。

実装

今回はダークモードの切り替えを例とする。

フォルダはnextっぽい感じで以下。

.
└── src
      ├── components
      │   └── toggle.tsx
      ├── contexts
      │   └── theme.ts
      └── pages
           └── index.tsx

useContextを使ってみる

Docsの通り。

Contextを作成したら、Providerでラップする。

pages/index.tsx
import React from 'react';

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

const Index: React.FC = () => {
  return (
    <ThemeContext.Provider value={themes.dark}>
      ...
      <ToggleButton />
    </ThemeContext.Provider>
  );
}

export default Index;

子コンポーネントでは・・・

components/toggle.tsx
import React from 'react';

const ToggleButton: React.FC = () => {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

ただこれでは、子コンポーネントからthemeを変更することができない。
(useContextは基本参照用の機能として提供されている。)

なので、Contextを変更できるカスタムフックを作成する。

カスタムフックの作成

簡略のため、darkかどうかのbooleanをcontextとして扱うことにする。

context/theme.ts
import { createContext, useCallback, useState } from 'react';

// set context type
type ThemeContext = {
  dark: boolean;
  setIsDark: (isDark: boolean) => void;
};

// context default value
const defaultContext: ThemeContext = {
  dark: false,
  // 初期値を作成するが、eslintに引っかかる
  // eslint-disable-next-line @typescript-eslint/no-empty-function
  setIsDark: () => {},
};

// context object
export const themeContext = createContext<ThemeContext>(defaultContext);

// custom Hook
export const useDark = (): ThemeContext => {
  // state名はThemeContext typeのプロパティに合わせる。
  const [dark, setDark] = useState(false);
  // 関数名はThemeContext typeのプロパティに合わせる。
  const setIsDark = useCallback((current: boolean): void => {
    setDark(current);
  }, []);
  return {
    dark,
    setIsDark,
  };
};

利用する

親コンポーネントでは先ほどのカスタムフックを利用、Providerにカスタムフックから受け取った値を渡す

pages/index.tsx
import React from 'react';

import { themeContext, useDark } from '../contexts/theme';
import { ToggleButton } from "../components/toggle"

const Index: NextPage = () => {
  const ctx = useDark();
  return (
    <themeContext.Provider value={ctx}>
      ...
      <ToggleButton />
    </themeContext.Provider>
  );
};

子ではuseContextを利用し、contextから値と更新用関数を受け取る。

components/toggle.tsx
import React, { useContext } from 'react';

import { themeContext } from '../contexts/align';

const toggleButton: React.FC = () => {
  const ctx = useContext(themeContext);
  const handleClick = () => {
      ctx.setIsDark(!ctx.dark) // Context値更新
  }
  return (
    <button onClick={handleClick}>
     Toggle theme context!
    </button>
  );
}

これでuseContextとcontextオブジェクトをインポートすればどこからでもcontextを操作できる。

60
45
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
45