2022/07/03
You Might Not Need an Effect
Resetting all state when a prop changes
悪い例
import { useEffect, useState } from "react"; const App = () => { const [userId, setUserId] = useState(0); return ( <> <button onClick={() => setUserId((userId) => userId + 1)}>+1</button> <Comment userId={userId} /> </> ); }; type CommentProps = { userId: number, }; const Comment = (props: CommentProps) => { const { userId } = props; const [comment, setComment] = useState(""); useEffect(() => setComment(""), [userId]); return ( <> <div>{userId}</div> <input value={comment} onChange={(e) => setComment(e.target.value)} /> <div>{comment}</div> </> ); }; export default App;
userId
ごとにコメントを出し分けるComment
コンポーネントが存在する。userId
が変化した時に,comment
state を初期化するために,Effect を使用している。
userId
state が更新された時に,Comment
コンポーネントが古い値のまま再レンダリングされ,その後 useEffect により,もう一度レンダリング処理が走るため,非効率。
良い例
import { useState } from "react"; const App = () => { const [userId, setUserId] = useState(0); return ( <> <button onClick={() => setUserId((userId) => userId + 1)}>+1</button> <Comment userId={userId} key={userId} /> </> ); }; type CommentProps = { userId: number, }; const Comment = (props: CommentProps) => { const { userId } = props; const [comment, setComment] = useState(""); return ( <> <div>{userId}</div> <input value={comment} onChange={(e) => setComment(e.target.value)} /> <div>{comment}</div> </> ); }; export default App;
Comment
コンポーネントにuserId
を key として持たせることで解決できる。key が違うと別のコンポーネントとみなされるため,userId
が変化する度に,comment
state は初期化される。
Initializing the application
初期マウント時に一度だけ行いたい処理に useEffect を使用した時,開発環境では2回レンダリングが起こる。ログイン処理など,一度だけ行われるべき処理では,以下のように対処すべき。
import { useEffect } from "react"; let didInit = false; const App = () => { useEffect(() => { if (didInit) return; didInit = true; console.log("Hello world"); }, []); return ( <> <h1>Hello World</h1> </> ); }; export default App;
Subscribing to an external store
外部から取得したデータについて考える。
悪い例
import { useEffect, useState } from "react"; const useOnlineStatus = () => { const [isOnline, setIsOnline] = useState(true); useEffect(() => { const updateState = () => { setIsOnline(navigator.onLine); }; updateState(); window.addEventListener("online", updateState); window.addEventListener("offline", updateState); return () => { window.removeEventListener("online", updateState); window.removeEventListener("offline", updateState); }; }, []); return isOnline; }; const App = () => { const isOnline = useOnlineStatus(); if (isOnline) { console.log("Online!"); } else { console.log("Offline!"); } return <h1>Hello World</h1>; }; export default App;
navigator.onLine
というブラウザのオンライン状態を返すために Effect を使用している。
良い例
useSyncExternalStore
という React 独自の hook を使用する。
import { useSyncExternalStore } from "react"; const subscribe = (callback: () => void) => { window.addEventListener("online", callback); window.addEventListener("offline", callback); return () => { window.removeEventListener("online", callback); window.removeEventListener("offline", callback); }; }; const useOnlineStatus = () => { return useSyncExternalStore( subscribe, () => navigator.onLine, () => true ); }; const App = () => { const isOnline = useOnlineStatus(); if (isOnline) { console.log("Online!"); } else { console.log("Offline!"); } return <h1>Hello World</h1>; }; export default App;