概要
Astroのアイランドアーキテクチャを活用した実装において、SSRとCSRを併用したい場面がありました。
具体的には、記事検索機能の実装場面で
- 初期検索結果の取得 → サーバー側でfetchしてSSR
- 2回目以降の検索結果の取得 → 検索条件の変更をトリガーにClientでfetchしてCSR
という要件を満たしたいということがありました。
後述のコードのように実装したのですが、これには「初回レンダリング時にせっかくサーバー側でデータを取得しても、client側で再度fetchしてしまいローディングUIが表示されてしまう。」という問題がありました。
const [isLoading, setIsLoading] = useState(false)
const [data, setData] = useState([])
useEffect(() => {
setIsLoading(true)
fetchData({
...searchParams,
}).then((res) => {
setData(res ?? [])
}).finally(() => {
setIsLoading(false)
})
}, [searchParams])
解決方法とコードの例
「初回レンダリング時にせっかくサーバー側でデータを取得しても、client側でも再度fetchしてしまいローディングUIが表示されてしまう。」という問題を解決するには、初回のレンダリング時はfetchData
を実行しないようにする必要がありました。
これは、以下のコードで解決できました。useRef
を使用して最初のレンダリングか否かを保持することで、新しいレンダーをトリガしないことを担保しつつ、1回目のレンダリング時のみfetchをしないということを実現しています。
const [isLoading, setIsLoading] = useState(false)
const [data, setData] = useState([])
+ const isFirstRender = useRef(true) // refはコンポーネントに情報を記憶させたいが、新しいレンダーをトリガしたくない場合に有効
useEffect(() => {
+ if (isFirstRender.current) {
+ isFirstRender.current = false // ここでfalseに更新することで2回目以降のレンダリング時は正常にfetchが行われる。
+ return
+ }
setIsLoading(true)
fetchData({
...searchParams,
}).then((res) => {
setData(res ?? [])
}).finally(() => {
setIsLoading(false)
})
}, [searchParams])