🐢

コンポーネントのpropsに渡す代わりにuseContextを使う

に公開
2026/02/03
1件

たくさんの子コンポーネントを作成して
同じstateを参照したい場合、
親コンポーネントに作成したstateを渡すのが
おっくうになったので代替できないのかとおもって
ためしてみた記録です

環境

nextjsのcreate-next-app@latestを実行してプロジェクトを作成
https://nextjs.org/docs/app/getting-started/installation
プロジェクト設定はこちら
デフォルトを指定しています

% npx create-next-app@latest
Need to install the following packages:
create-next-app@16.1.6
✔ What is your project named? … my-app
✔ Would you like to use the recommended Next.js defaults? › Yes, use recommended defaults
Using npm.

Initializing project with template: app-tw 

Installing dependencies:
- next
- react
- react-dom

Installing devDependencies:
- @tailwindcss/postcss
- @types/node
- @types/react
- @types/react-dom
- eslint
- eslint-config-next
- tailwindcss
- typescript

デバック実行

npm run dev

完成画面キャプチャ

ファイル情報を複数コンポーネントで共有して、
各コンポーネントがそれぞれの情報を表示することを目標にしています

完成画面キャプチャ

コンポーネント構成

app
├── components
│    ├── ShowModifiedDate.tsx   -- ファイルの変更日を表示するコンポーネント
│    ├── ShowName.tsx           -- ファイル名を表示するコンポーネント
│    └── ShowSize.tsx           -- ファイルサイズを表示するコンポーネント
├── file
│    └── upload
│         └── Page.tsx          -- 表示するページ
└── lib
     └── StoredFileProvider.tsx -- アップロードしたファイル情報を保持するContext

ファイル情報を保持するContext

ファイル情報を保持するためにcreateContextを使います
createContextは参照のみできるものなので、この値を更新させるために
useReducerも利用します

https://ja.react.dev/reference/react/createContext
https://ja.react.dev/reference/react/useReducer

app/lib/StoredFileProvider.tsx
import { createContext } from "react";

export function reducer(state: FileList | null, action: FileList | null) {
  return action || state;
}

export const StoredFile = createContext(null as (FileList | null));

ページに適用

ページ内でファイルをアップロードした際に、ファイル情報を更新します
StoredFileコンテキストにstateを渡し、最新のファイル情報を保持します

app/file/upload/Page.tsx
'use client';

import { useReducer } from "react";
import { reducer, StoredFile } from "@/app/lib/StoredFileProvider";

export default function Page() {
  const [state, dispatch] = useReducer(reducer, null);

  return (
    <div>
      <input type="file" name="" id="" onChange={(e) => { dispatch(e.target.files); }} />
      <StoredFile
        value={state}
      >
      </StoredFile>
    </div>
  );
}

子コンポーネントを用意

それぞれのコンポーネントでuseContextを呼び出す

app/components/ShowModifiedDate.tsx
import { useContext } from "react"
import { StoredFile } from "../lib/StoredFileProvider"

export const ShowModifiedDate = () => {
  const state = useContext(StoredFile);

  // 最終更新日
  const date = new Date(state?.item(0)?.lastModified || '');

  // 空文字を指定している場合、`invalid Date`となるので、isNaNでない場合のみ画面表示 
  return (
    <span style={{ color: 'blue' }}>{!isNaN(date.getTime()) && date.toLocaleDateString()}</span>
  )
}
app/components/ShowName.tsx
import { useContext } from "react"
import { StoredFile } from "../lib/StoredFileProvider"

export const ShowName = () => {
  const state = useContext(StoredFile);

  // ファイル名 
  return (
    <span style={{ color: 'red' }}>{state?.item(0)?.name}</span>
  )
}
app/components/ShowSize.tsx
import { useContext } from "react"
import { StoredFile } from "../lib/StoredFileProvider"

export const ShowSize = () => {
  const state = useContext(StoredFile);

  // ファイルサイズ
  return (
    <span style={{ color: 'green' }}>{state?.item(0)?.size}</span>
  )
}

子コンポーネントにわたす

コンポーネントにstateを渡す必要がなくなりすっきりしました

app/file/upload/Page.tsx
    <div>
      <input type="file" name="" id="" onChange={(e) => { dispatch(e.target.files); }} />
      <StoredFile
        value={{
          state
        }}>
        <div style={{ display: 'grid' }}>
+          <ShowName />
+          <ShowSize />
+          <ShowModifiedDate />
        </div>
      </StoredFile>
    </div>

まとめ

useStateを使わなくてもできることがわかりました
しかし
ファイル情報を保持するだけなら、
従来通りuseStateを使ったほうが処理が明確で
わかりやすい気がしました

2026/02/03 追記
本来は簡潔に書けます

app/file/upload/Page.tsx
'use client';

import { useState } from "react";

export default function Page() {
  const [file, setFile] = useState<FileList | null>(null);

  const date = new Date(file?.item(0)?.lastModified || '');

  return (
    <div>
      <input type="file" name="" id="" onChange={(e) => { setFile(e.target.files); }} />
        <div style={{ display: 'grid' }}>
          <span style={{ color: 'red' }}>{file?.item(0)?.name}</span>
          <span style={{ color: 'green' }}>{file?.item(0)?.size}</span>
          <span style={{ color: 'blue' }}>{!isNaN(date.getTime()) && date.toLocaleDateString()}</span>
        </div>
    </div>
  );
}

Discussion

Honey32Honey32

ファイル情報を保持するだけなら、
従来通りuseStateを使ったほうが処理が明確で
わかりやすい気がしました

失礼します。この直感は正しいと思います。

  • 状態の「保持」
    • useState
    • useReducer(useState よりちょっと凝ったことができる)
  • 状態の「共有」
    • Props
    • Context
    • または、そもそも「共有」を不要にする方法もある
      • Composition パターン
      • そもそもコンポーネントを分けない
  • 具体的なロジック(ファイル名やファイルサイズの抽出)

ロジックの複雑さは、この3つの要素に切り分けできるのですが、今回のようなケースの難しさはほとんど「具体的なロジック」しかないので、

「useState」と「そもそもコンポーネントを分けない」の組み合わせで十分で、しかもそうすればシンプルに片付けられるので読みやすくなると思います!

▼ことわりなく「コンポジション」を出してしまいましたが、以下の記事で解説しています
https://qiita.com/honey32/items/4d04e454550fb1ed922c