useRefを描画に利用しようとした者の末路
useRef は中身が変更になってもそのことを通知しないということを覚えておいてください。.current プロパティを書き換えても再レンダーは発生しません。
.current
が書き換わっても再レンダーは発生しないので、描画の制御に .current
を利用するのはやめましょう
例えばformをリセットする様な、refの対象をコントロールする機能を実装するとします
このようなコードなら上手く動作します
import "./styles.css";
import { useRef } from "react";
export default function App() {
const r = useRef<HTMLFormElement>(null);
const onControll = () => {
console.log("onControll", r.current);
r.current?.reset();
};
console.log("render", r.current);
return (
<div className="App">
<form ref={r}>
<input />
</form>
<button onClick={onControll}>refをコントロール</button>
</div>
);
}
しかし初期にformを表示できない場合はどうでしょう?
例えばあなたがNextJSなどで開発している場合、SSGの初期Loadingを考慮する必要が発生することがあるかもしれません。
ここでの過ちは .current
を使って準備が整うまでボタンを使用不可にしたことです。
追加のレンダリングが走るまで、ボタンはずっと使用不可のままになってしまいます。
import "./styles.css";
import { useRef, useState } from "react";
export default function App() {
const r = useRef<HTMLFormElement>(null);
const [show, setShow] = useState(false);
const [show2, setShow2] = useState(false);
const onControll = () => {
console.log("onControll", r.current);
r.current?.reset();
};
console.log("render", r.current);
return (
<div className="App">
<div>SSGなどで特定の条件下でのみ表示したいようなケース</div>
<div>
formの部分はSkeletonなどLoading表示しつつ、refのコントローラーはdisabledにしておきたい
</div>
<button
onClick={() => {
setShow(true);
}}
>
条件を満たす
</button>
<button
onClick={() => {
setShow2(true);
}}
>
再レンダリング
</button>
<button onClick={onControll} disabled={Boolean(!r.current)}>
refのコントローラー
</button>
{show && (
<form ref={r}>
<input />
</form>
)}
{show2 && <div>show2のフラグが有効化</div>}
</div>
);
}
なので useRef を描画の制御に利用するのはやめましょう。
そして出来ればドキュメントはちゃんと目を通しておきましょう。
上に挙げたコードには codesandbox のリンクも添えてあるので実際に触って試してみてください
[おまけ]
useRef は中身が変更になってもそのことを通知しないということを覚えておいてください。.current プロパティを書き換えても再レンダーは発生しません。DOM ノードを ref に割り当てたり割り当てを解除したりする際に何らかのコードを走らせたいという場合は、コールバック ref を代わりに使用してください。
コールバック ref を使ってみましょう。
いい感じですね!
この場合 useRef<HTMLFormElement>(null)
ではなく useRef<HTMLFormElement>()
にする必要があります。前者は RefObject が、後者は MutableRefObject が返ってきます。MutableRefObject は r.current = node;
のような代入が可能です。
import "./styles.css";
import { useCallback, useRef, useState } from "react";
export default function App() {
const [ready, setReady] = useState(false);
const r = useRef<HTMLFormElement>();
const cr = useCallback((node: HTMLFormElement) => {
if (node !== null) {
console.log("node not null");
setReady(true);
r.current = node;
}
}, []);
const [show, setShow] = useState(false);
const onControll = () => {
console.log("onControll", r.current);
r.current?.reset();
};
console.log("render", r.current);
return (
<div className="App">
<div>SSGなどで特定の条件下でのみ表示したいようなケース</div>
<div>
formの部分はSkeletonなどLoading表示しつつ、refのコントローラーはdisabledにしておきたい
</div>
<button
onClick={() => {
setShow(true);
}}
>
条件を満たす
</button>
<button onClick={onControll} disabled={Boolean(!ready)}>
refのコントローラー
</button>
{show && (
<form ref={cr}>
<input />
</form>
)}
</div>
);
}