2
2

1. この記事について

React公式の状態管理機能であるContext APIの使い方メモです
ReactとRemixでの利用について書きます

2. Context APIとは

prop drillingダルいよね
という時にReact標準で使えるstate管理機能です
Reduxと似たようなことが出来るようです

3. ReactでContext APIを使う

以下の3つのステップでstateを共有できます

  1. 渡したいstateや更新する為の関数を定義したContextを作る
  2. Contextを渡す範囲をProviderで指定する
  3. useContextでContextを呼び出してstateや関数を使う

3-1. ContextとProviderをつくる

React.createContextでContextを作り、これをProviderに組み込んで使います

context.tsx
import React, { useState, ReactNode } from 'react';

type CountContextType = {
  count: number;
  setCount: (newCount: number) => void;
} | null
export const CountContext = React.createContext<CountContextType>(null);

export function CountProvider({ children }: { children: ReactNode }) {
  const [count, setCount] = useState<number>(0);

  return (
    <CountContext.Provider value={{ count, setCount }}>
      {children}
    </CountContext.Provider>
  );
}

3-2. Contextを渡す範囲をProviderで指定する

Providerで囲んでやれば囲まれているすべてのコンポーネントでContextが利用可能になります

app.tsx
import { CountProvider } from './context'
export function App() {
  return (
    <CountProvider>
      <ComponentA />
      <ComponentB />
    </CountProvider>
  )
}

上記の場合はComponentAとComponentBで利用可能になります
直接でなく子コンポーネントや孫コンポーネントでも利用可能です

3-3. useContextで呼び出して利用する

useContextで作成済みのContextを呼んでくるとProviderを定義したときにvalueに渡した値や関数を受け取れます。useState単独でなく、複数のstateやもっと複雑な処理をする関数を作って共有することも出来ます

import { useContext } from 'react';
import { CountContext } from './context'

export default function ComponentA() {
  const context = useContext(CountContext);

  if (context === null) {
    throw new Error('CountButton must be used within a CountProvider');
  }

  const { count, setCount } = context;

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={incrementCount}>Click me!</button>
      <p>ComponentA {count}</p>
    </div>
  );
}

export default function ComponentB() {
  const context = useContext(CountContext);

  if (context === null) {
    throw new Error('CountButton must be used within a CountProvider');
  }

  const { count, setCount } = context;

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={incrementCount}>Click me!</button>
      <p>ComponentB {count}</p>
    </div>
  );
}

image.png

同じstateを共有しているのでどちらをクリックしても両方がカウントアップされるようになりました

4. RemixでContext APIを使う

RemixのNested Routeのようにナビゲーションやサイドバーとメイン画面を分けて書くときもContext APIが使えます

4-1. ContextとProviderをつくる

ここは同じです

4-2. Contextを渡す範囲をProviderで指定する

Providerで囲んでやれば良いのは同じです

/root.tsx
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "@remix-run/react";
import "./tailwind.css";

import React, { useContext } from 'react';
import { CountProvider, CountContext } from './routes/context'

function Header() {
  const context = useContext(CountContext);

  if (context === null) {
    throw new Error('CountButton must be used within a CountProvider');
  }

  const { count } = context;

  return (
    <header>
      <nav>
        へっだー部分です: { count }
      </nav>
    </header>
  )
}

export function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <CountProvider>
          <Header />
          {children}
        </CountProvider>
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}

4-3. useContextで呼び出して利用する

後は使いたいコンポーネントで呼び出して使うだけです

/routes/_index.tsx
import { useContext } from 'react';
import { CountContext } from './context'


export default function Index() {
  const context = useContext(CountContext);

  if (context === null) {
    throw new Error('CountButton must be used within a CountProvider');
  }

  const { count, setCount } = context;

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={incrementCount}>Click me!</button>
      <p>本文部分です {count}</p>
    </div>
  );
}

image.png

できました

5. まとめ

prop drillingに悩まされていたのが解消できそうです

また、Contextを分けて書いておけるのが分かりやすく、リファクタリングや設計変更でアプリのコンポーネント構造が変わるときにもすごく役立ちそう

コンポーネント内でのみ使うstateは普通にコンポーネントで書いておく方が簡単なので、使い分けるのが良さげです

2
2
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
2
2