異なるデータを用いたクエリの再取得
「クエリの再取得」とは、クエリによって最初にレンダリングされたデータとは異なるデータに対してクエリを再度フェッチすることを意味します。たとえば、現在選択されているアイテムを変更したり、表示されているアイテムとは異なるアイテムのリストをレンダリングしたり、より一般的には、現在レンダリングされているコンテンツを新しいコンテンツまたは異なるコンテンツを表示するように遷移したりする場合などです。
useQueryLoader
/ loadQuery
を使用する場合
useQueryLoader
を用いたクエリの更新と同様に、レンダリングのためのクエリのフェッチセクションで説明したuseQueryLoader
Hookを使用することもできますが、今回は異なるクエリ変数を渡します。
/**
* App.react.js
*/
const AppQuery = require('__generated__/AppQuery.graphql');
function App(props: Props) {
const variables = {id: '4'};
const [queryRef, loadQuery] = useQueryLoader(
AppQuery,
props.appQueryRef /* initial query ref */
);
const refetch = useCallback(() => {
// Load the query again using the same original variables.
// Calling loadQuery will update the value of queryRef.
loadQuery({id: 'different-id'});
}, [/* ... */]);
return (
<React.Suspense fallback="Loading query...">
<MainContent
refetch={refetch}
queryRef={queryRef}
/>
</React.Suspense>
);
}
/**
* MainContent.react.js
*/
// Renders the preloaded query, given the query reference
function MainContent(props) {
const {refetch, queryRef} = props;
const data = usePreloadedQuery(
graphql`
query AppQuery($id: ID!) {
user(id: $id) {
name
friends {
count
}
}
}
`,
queryRef,
);
return (
<>
<h1>{data.user?.name}</h1>
<div>Friends count: {data.user?.friends?.count}</div>
<Button
onClick={() => refetch()}>
Fetch latest count
</Button>
</>
);
}
ここで何が起こっているのかを詳しく見てみましょう。
- 再取得のイベントハンドラで
loadQuery
を呼び出すため、ネットワークリクエストはすぐに開始され、次にqueryRef
をusePreloadedQuery
に渡すことで、更新されたデータがレンダリングされます。 loadQuery
にfetchPolicy
を渡していないため、デフォルト値の'store-or-network'
が使用されます。 (レンダリングのためのキャッシュデータの再利用で説明したように)ローカルにキャッシュされたデータを使用するかどうかを指定するために、異なるポリシーを提供することもできます。loadQuery
を呼び出すと、コンポーネントが再レンダリングされ、usePreloadedQuery
がサスペンドする可能性があります(Suspenseを用いたローディング状態で説明)。これは、フォールバックのローディング状態を表示するために、MainContent
コンポーネントをラップするSuspense
境界があることを確認する必要があることを意味します。
Suspenseを回避する必要がある場合
場合によっては、Suspenseのフォールバックの表示を避けたい場合があります。フォールバックを表示すると、すでにレンダリングされているコンテンツが非表示になってしまいます。このような場合は、fetchQuery
を使用し、ローディング状態を手動で追跡することができます。
Reactの将来のバージョンで並行レンダリングがサポートされるようになると、Reactはこのケースをサポートし、サスペンド時にすでにレンダリングされているコンテンツをSuspenseフォールバックで非表示にすることを回避するオプションを提供する予定です。
/**
* App.react.js
*/
const AppQuery = require('__generated__/AppQuery.graphql');
function App(props: Props) {
const environment = useRelayEnvironment();
const [queryRef, loadQuery] = useQueryLoader(
AppQuery,
props.appQueryRef /* initial query ref */
);
const [isRefetching, setIsRefetching] = useState(false)
const refetch = useCallback(() => {
if (isRefetching) { return; }
setIsRefetching(true);
// fetchQuery will fetch the query and write
// the data to the Relay store. This will ensure
// that when we re-render, the data is already
// cached and we don't suspend
fetchQuery(environment, AppQuery, variables)
.subscribe({
complete: () => {
setIsRefetching(false);
// *After* the query has been fetched, we call
// loadQuery again to re-render with a new
// queryRef.
// At this point the data for the query should
// be cached, so we use the 'store-only'
// fetchPolicy to avoid suspending.
loadQuery({id: 'different-id'}, {fetchPolicy: 'store-only'});
},
error: () => {
setIsRefetching(false);
}
});
}, [/* ... */]);
return (
<React.Suspense fallback="Loading query...">
<MainContent
isRefetching={isRefetching}
refetch={refetch}
queryRef={queryRef}
/>
</React.Suspense>
);
}
ここで何が起こっているのかを詳しく見てみましょう。
- 再取得時には、サスペンドを回避しているため、独自の
isRefetching
ローディング状態を追跡します。 この状態を使用して、MainContent
を非表示にすることなく、MainContent
コンポーネント内にビジー スピナーまたは同様のローディングUIをレンダリングできます。 - イベントハンドラでは、最初に
fetchQuery
を呼び出します。これにより、クエリがフェッチされ、データがローカルのRelayストアに書き込まれます。fetchQuery
ネットワークリクエストが完了すると、loadQuery
を呼び出して、更新されたqueryRef
を取得します。これをusePreloadedQuery
に渡して、前の例と同様に更新されたデータをレンダリングします。 - この時点で、
loadQuery
が呼び出されると、クエリのデータはすでにローカルのRelayストアにキャッシュされているはずなので、fetchPolicy
に'store-only'
を使用してサスペンドを回避し、すでにキャッシュされているデータのみを読み取ります。
useLazyLoadQuery
を使用する場合
useLazyLoadQuery
を用いたクエリの更新と同様に、useLazyLoadQuery
Hook (レンダリング中のクエリの遅延フェッチセクションで説明) を使用することもできますが、今回は異なるクエリ変数を渡します。
/**
* App.react.js
*/
const AppQuery = require('__generated__/AppQuery.graphql');
function App(props: Props) {
const [queryArgs, setQueryArgs] = useState({
options: {fetchKey: 0},
variables: {id: '4'},
});
const refetch = useCallback(() => {
// Trigger a re-render of useLazyLoadQuery with new variables,
// *and* an updated fetchKey.
// The new fetchKey will ensure that the query is fully
// re-evaluated and refetched.
setQueryArgs(prev => ({
options: {
fetchKey: (prev?.options.fetchKey ?? 0) + 1,
},
variables: {id: 'different-id'}
}));
}, [/* ... */]);
return (
<React.Suspense fallback="Loading query...">
<MainContent
refetch={refetch}
queryArgs={queryArgs}
/>
</React.Suspense>
);
}
/**
* MainContent.react.js
*/
// Fetches and renders the query, given the fetch options
function MainContent(props) {
const {refetch, queryArgs} = props;
const data = useLazyLoadQuery(
graphql`
query AppQuery($id: ID!) {
user(id: $id) {
name
friends {
count
}
}
}
`,
queryArgs.variables,
queryArgs.options,
);
return (
<>
<h1>{data.user?.name}</h1>
<div>Friends count: {data.user.friends?.count}</div>
<Button
onClick={() => refetch()}>
Fetch latest count
</Button>
</>
);
}
ここで何が起こっているのかを詳しく見てみましょう。
- 更新のイベントハンドラで、状態に新しいクエリ引数を設定することでコンポーネントを更新します。 これにより、
useLazyLoadQuery
を使用するMainContent
コンポーネントが新しいvariables
とfetchKey
で再レンダリングされ、レンダリング時にクエリが再取得されます。 - 更新ごとにインクリメントする
fetchKey
の新しい値を渡しています。 更新ごとに新しいfetchKey
をuseLazyLoadQuery
に渡すと、クエリが完全に再評価され、再取得されることが保証されます。 useLazyLoadQuery
に新しいfetchPolicy
を渡していないため、デフォルト値の'store-or-network'
が使用されます。 (レンダリングのためのキャッシュデータの再利用で説明したように)ローカルにキャッシュされたデータを使用するかどうかを指定するために、異なるポリシーを提供することもできます。refetch
の状態更新により、コンポーネントが再レンダリングされ、コンポーネントがサスペンドする可能性があります(Suspenseを用いたローディング状態で説明)。これは、フォールバックのローディング状態を表示するために、MainContent
コンポーネントをラップするSuspense
境界があることを確認する必要があることを意味します。
Suspenseを回避する必要がある場合
場合によっては、Suspenseのフォールバックの表示を避けたい場合があります。フォールバックを表示すると、すでにレンダリングされているコンテンツが非表示になってしまいます。このような場合は、fetchQuery
を使用し、ローディング状態を手動で追跡することができます。
Reactの将来のバージョンで並行レンダリングがサポートされるようになると、Reactはこのケースをサポートし、サスペンド時にすでにレンダリングされているコンテンツをSuspenseフォールバックで非表示にすることを回避するオプションを提供する予定です。
/**
* App.react.js
*/
const AppQuery = require('__generated__/AppQuery.graphql');
function App(props: Props) {
const environment = useRelayEnvironment();
const [isRefreshing, setIsRefreshing] = useState(false)
const [queryArgs, setQueryArgs] = useState({
options: {fetchKey: 0, fetchPolicy: 'store-or-network'},
variables: {id: '4'},
});
const refetch = useCallback(() => {
if (isRefreshing) { return; }
setIsRefreshing(true);
// fetchQuery will fetch the query and write
// the data to the Relay store. This will ensure
// that when we re-render, the data is already
// cached and we don't suspend
fetchQuery(environment, AppQuery, variables)
.subscribe({
complete: () => {
setIsRefreshing(false);
// *After* the query has been fetched, we update
// our state to re-render with the new fetchKey
// and fetchPolicy.
// At this point the data for the query should
// be cached, so we use the 'store-only'
// fetchPolicy to avoid suspending.
setQueryArgs(prev => ({
options: {
fetchKey: (prev?.options.fetchKey ?? 0) + 1,
fetchPolicy: 'store-only',
},
variables: {id: 'different-id'}
}));
},
error: () => {
setIsRefreshing(false);
}
});
}, [/* ... */]);
return (
<React.Suspense fallback="Loading query...">
<MainContent
isRefetching={isRefetching}
refetch={refetch}
queryArgs={queryArgs}
/>
</React.Suspense>
);
}
ここで何が起こっているのかを詳しく見てみましょう。
- 再取得時には、サスペンドを回避しているため、独自の
isRefetching
ローディング状態を追跡します。 この状態を使用して、MainContent
を非表示にすることなく、MainContent
コンポーネント内にビジー スピナーまたは同様のローディングUIをレンダリングできます。 - イベントハンドラでは、最初に`fetchQuery`を呼び出します。これにより、クエリがフェッチされ、データがローカルのRelayストアに書き込まれます。 `fetchQuery`ネットワークリクエストが完了すると、状態を更新して、更新された`fetchKey`と`fetchPolicy`を再レンダリングします。これを`useLazyLoadQuery`に渡して、前の例と同様に更新されたデータをレンダリングします。/p>
- この時点で、状態を更新すると、クエリのデータはすでにローカルのRelayストアにキャッシュされているはずなので、`fetchPolicy`に`'store-only'`を使用してサスペンドを回避し、すでにキャッシュされているデータのみを読み取ります。
このページは役に立ちましたか?
いくつかの簡単な質問に答えることで、 サイトの改善にご協力ください。.