できない.dev

React Context の値が更新されない

Context の更新が consumer に伝わらない事象は、value の参照が変わっていないか、Provider 自体が毎レンダーで再マウントしているのが典型。
`useState` / `useReducer` を経由して値を渡し、複合オブジェクトは `useMemo` で参照を安定させる。

#react#context#provider#state#rerender

公開:

要約

Context の更新が consumer に届かない事象は、ほぼ「value の参照が変わっていない」か「Provider 自体が再マウントされて値が初期化される」のどちらかに分類できる。
React は Object.is で前回 value と比較し、参照が変わった配下の consumer だけ再レンダーする。let で書き換えた値や ref を value に詰めても発火しない。

よくある原因

  1. 可変変数 / ref を value に詰める: モジュール変数や let 変数、useRef().current を value に渡し中身を書き換えるパターンは、参照が同一なので React からは「変わっていない」と見える。
  2. state を経由していない: 「再レンダーする」のは state(および props)の変化が起点。
    setState を経由しない更新は consumer に伝わらない。
  3. Provider の再マウント: 親で key を毎レンダー変える、条件分岐で Provider が外れて戻る、などで内部 state が初期化され、見かけ「値が更新されない」に見える。
  4. 取り違え: ネストした複数 Provider のうち、useContext の引数が想定と違う Context を指している。

解決策

1. useState を value に渡す

const UserContext = createContext<{
  user: User | null;
  setUser: (u: User) => void;
} | null>(null);
 
export function UserProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const value = useMemo(() => ({ user, setUser }), [user]);
  return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}

createContext のリファレンス の通り、useState の戻り値を useMemo で包んでから渡せば、user が変わったときだけ参照も更新される。

2. ref ではなく state で持つ

// NG: ref.current の書き換えは React に通知されない
const ref = useRef({ count: 0 });
return <Ctx.Provider value={ref.current}>...</Ctx.Provider>;
 
// OK: state を経由
const [count, setCount] = useState(0);
return <Ctx.Provider value={{ count, setCount }}>...</Ctx.Provider>;

ref は描画外の可変保存用、「変わったら描画」は state の役割。

3. Provider を安定的にマウント

// NG: key が毎レンダー変わって再マウント
<UserProvider key={Math.random()}>...</UserProvider>
 
// OK: ルート近くで一度だけマウント
<UserProvider>
  <App />
</UserProvider>

key の変更は state を意図的にリセットする手段であり、意図せず使うと「更新されない」ではなく「初期値に戻る」現象になる。

4. DevTools で実値を確認

React DevTools → Components → 該当 Provider を選ぶと、現在の value と consume している子孫一覧が見える。
値が更新されているのに consumer が再レンダーしないなら、useContext の引数か上位 Provider の階層を疑う。

この記事は役立ちましたか?