そもそもuseOutletContextとはなにか?
useOutletContextは、Reactにおいて親コンポーネントで定義したstateの値を子コンポーネントから変更するものです。イメージとしては以下の図になります。
通常、Reactでは Single-Source-of-Truthに則って、親コンポーネントで定義したstateの値を子コンポーネントで使うためには props を使います。
しかし、これでは親コンポーネントで定義したstateの値を子コンポーネント側から変更することができません。そこで、useOutletContextを使い、子コンポーネント側からstateの値を変更できるようにします。
単純な話、親コンポーネントで定義したstateの値を子コンポーネントに渡すのは簡単で、propsを使えば良いのです。しかしpropsでは子コンポーネント側からstateの値が変更できません。なので、そんな時にはuseOutletContextを使います。useOutletContextはreact-router v6から追加されたもので、v6は2021年11月3日にリリースされました。なのでまともなリソースは chat GPT含めてほぼ見当たりません。stackoverflowには若干Q&Aがあります。
useOutletContextの使い方
さて、useOutletContextの公式サイトを見に行くと以下のようなサンプルコードが手に入ります。
import * as React from "react";
import type { User } from "./types";
import { Outlet, useOutletContext } from "react-router-dom";
type ContextType = { user: User | null };
export default function Dashboard() {
const [user, setUser] = React.useState<User | null>(null);
return (
<div>
<h1>Dashboard</h1>
<Outlet context={{ user } satisfies ContextType} />
</div>
);
}
export function useUser() {
return useOutletContext<ContextType>();
}
import { useUser } from "../dashboard";
export default function DashboardMessages() {
const { user } = useUser();
return (
<div>
<h2>Messages</h2>
<p>Hello, {user.name}!</p>
</div>
);
}
javascriptの方は簡潔で良い感じなのですが、typescriptの方は冗長です。React.useStateなんて書き方はReact 16以降は必要なかったり、そもそもサンプルコードでは子コンポーネントの{user.name}を表示させるだけなので、ぶっちゃけuseOutletContextなんて使わずpropsで十分です。しかしこのサンプルコードから、親コンポーネントで定義したstateを子コンポーネントで使うにはconst {user}の形を取ることや、親コンポーネント側からuseUser()というfunctionを別途作成してexportする必要があることが分かります。
そんなわけで、サンプルコードが使いずらかったので幾つか修正して作ってみました。
import React, { useState } from 'react';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import 'react-tabs/style/react-tabs.css';
import { Link, Outlet, useOutletContext } from "react-router-dom";
import './App.css';
type ContextType = {
state: string | null
setState: React.Dispatch<React.SetStateAction<string>>
};
function App() {
const [state, setState] = useState("");
return (
<div>
<h1>Parent Component</h1>
<Tabs>
<TabList>
<Tab><Link to="/test">Test</Link></Tab>
</TabList>
<TabPanel>
<Outlet context={{state, setState}} />
</TabPanel>
</Tabs>
<p>State: {state}</p>
</div>
);
};
export default App;
export function useUser() {
return useOutletContext<ContextType>();
}
上が親コンポーネントで、下が子コンポーネントです。React-tabsを使ってタブ機能を追加しているので若干見づらいかもしれませんが、その辺は用途に応じてカスタマイズしてください。
import { useUser } from "../App"
const Test = () => {
const { state, setState } = useUser();
const handleClick = () => {
setState("abc");
};
return (
<div>
<h2>Child Component</h2>
<button onClick={handleClick}>Click me!</button>
</div>
);
};
export default Test;
親コンポーネント側でstateを定義して、子コンポーネント側からstateの値を変更できるようにしました。実際、コードを実行してみると、子コンポーネント側にあるクリックボタンを押せば親コンポーネント側で定義したstateの値が変更されることが分かると思います。こういう書き方をする場合、useOutletContextは有用です。しかし、こうしたstateの値の受渡や修正であれば、state状態管理ライブラリである jotaiや zustandを活用することを考えても良いでしょう。ミニマムな機能追加に留めたい、もしくはstateの管理がさほど難しくない(アプリが簡素である)場合には、zustandなどよりuseOutletContextを活用してみても良いかもしれません。
コメント