メインコンテンツにスキップ
バージョン: v18.0.0

クエリの更新

「クエリの更新」とは、サーバーから最新バージョンのデータを取得するために、クエリによって最初にレンダリングされたデータと全く同じデータをフェッチすることを意味します。

リアルタイム機能の使用

サーバーから最新バージョンのデータでデータを最新の状態に保ちたい場合、最初に検討すべきことは、データを定期的に手動で更新しなくても自動的に最新の状態に保ちやすくするリアルタイム機能を使用することが適切かどうかです。

この一例として、GraphQL サブスクリプションの使用があります。これには、サーバーとネットワーク層の追加設定が必要になります。

useQueryLoader / loadQuery を使用する場合

useQueryLoaderフックを使用してクエリを更新するには、レンダリングのためのクエリのフェッチセクションで説明したように、loadQueryをもう一度呼び出すだけで済みます。

/**
* App.react.js
*/

const AppQuery = require('__generated__/AppQuery.graphql');

function App(props: Props) {
const [queryRef, loadQuery] = useQueryLoader(
AppQuery,
props.appQueryRef /* initial query ref */
);

const refresh = useCallback(() => {
// Load the query again using the same original variables.
// Calling loadQuery will update the value of queryRef.
// The fetchPolicy ensures we always fetch from the server and skip
// the local data cache.
const {variables} = props.appQueryRef;
loadQuery(variables, {fetchPolicy: 'network-only'});
}, [/* ... */]);

return (
<React.Suspense fallback="Loading query...">
<MainContent
refresh={refresh}
queryRef={queryRef}
/>
</React.Suspense>
);
}
/**
* MainContent.react.js
*/

// Renders the preloaded query, given the query reference
function MainContent(props) {
const {refresh, 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={() => refresh()}>
Fetch latest count
</Button>
</>
);
}

ここで何が起こっているかを詳しく見てみましょう。

  • リフレッシュのイベントハンドラーでloadQueryを呼び出すと、ネットワークリクエストがすぐに開始され、更新されたqueryRefusePreloadedQueryを使用するMainContentコンポーネントに渡されるため、更新されたデータがレンダリングされます。
  • 'network-only'fetchPolicyを渡して、常にネットワークからフェッチし、ローカルデータキャッシュをスキップするようにしています。
  • loadQueryを呼び出すと、コンポーネントが再レンダリングされ、usePreloadedQueryが一時停止します(Suspenseによるローディング状態で説明されているように)。これは、使用しているfetchPolicyにより、ネットワークリクエストが常に作成されるためです。つまり、フォールバックのローディング状態を表示するために、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 [isRefreshing, setIsRefreshing] = useState(false)

const refresh = useCallback(() => {
if (isRefreshing) { return; }
const {variables} = props.appQueryRef;
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 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(variables, {fetchPolicy: 'store-only'});
}
error: () => {
setIsRefreshing(false);
}
});
}, [/* ... */]);

return (
<React.Suspense fallback="Loading query...">
<MainContent
isRefreshing={isRefreshing}
refresh={refresh}
queryRef={queryRef}
/>
</React.Suspense>
);
}

ここで何が起こっているかを詳しく見てみましょう。

  • リフレッシュする場合、一時停止を回避しているため、独自のisRefreshingローディング状態を追跡するようになりました。この状態を使用して、MainContentを非表示にすることなくMainContentコンポーネント内にビジースピナーまたは同様のローディングUIをレンダリングできます。
  • イベントハンドラーで、最初にfetchQueryを呼び出してクエリをフェッチし、データをローカルのRelayストアに書き込みます。fetchQueryネットワークリクエストが完了すると、loadQueryを呼び出して、更新されたqueryRefを取得し、それをusePreloadedQueryに渡して、前の例と同様に更新されたデータをレンダリングします。
  • この時点で、loadQueryが呼び出されると、クエリのデータはすでにローカルのRelayストアにキャッシュされているはずなので、一時停止を回避し、キャッシュ済みのデータのみを読み取るために、'store-only'fetchPolicyを使用します。

useLazyLoadQueryを使用する場合

useLazyLoadQueryフックを使用してクエリを更新するには、レンダリング中に遅延的にクエリをフェッチするセクションで説明したように、次の操作を実行できます。

/**
* App.react.js
*/
const AppQuery = require('__generated__/AppQuery.graphql');

function App(props: Props) {
const variables = {id: '4'};
const [refreshedQueryOptions, setRefreshedQueryOptions] = useState(null);

const refresh = useCallback(() => {
// Trigger a re-render of useLazyLoadQuery with the same variables,
// but an updated fetchKey and fetchPolicy.
// The new fetchKey will ensure that the query is fully
// re-evaluated and refetched.
// The fetchPolicy ensures that we always fetch from the network
// and skip the local data cache.
setRefreshedQueryOptions(prev => ({
fetchKey: (prev?.fetchKey ?? 0) + 1,
fetchPolicy: 'network-only',
}));
}, [/* ... */]);

return (
<React.Suspense fallback="Loading query...">
<MainContent
refresh={refresh}
queryOptions={refreshedQueryOptions ?? {}}
variables={variables}
/>
</React.Suspense>
);
/**
* MainContent.react.js
*/

// Fetches and renders the query, given the fetch options
function MainContent(props) {
const {refresh, queryOptions, variables} = props;
const data = useLazyLoadQuery(
graphql`
query AppQuery($id: ID!) {
user(id: $id) {
name
friends {
count
}
}
}
`,
variables,
queryOptions,
);

return (
<>
<h1>{data.user?.name}</h1>
<div>Friends count: {data.user.friends?.count}</div>
<Button
onClick={() => refresh()}>
Fetch latest count
</Button>
</>
);
}

ここで何が起こっているかを詳しく見てみましょう。

  • リフレッシュのイベントハンドラーで、状態に新しいオプションを設定することにより、コンポーネントを更新します。これにより、useLazyLoadQueryを使用するMainContentコンポーネントは、新しいfetchKeyfetchPolicyを使用して再レンダリングされ、レンダリング時にクエリを再フェッチします。
  • 更新ごとにインクリメントするfetchKeyの新しい値を渡しています。更新ごとに新しいfetchKeyuseLazyLoadQueryに渡すと、クエリが完全に再評価および再フェッチされるようになります。
  • 'network-only'fetchPolicyを渡して、常にネットワークからフェッチし、ローカルデータキャッシュをスキップするようにしています。
  • refreshの状態更新により、コンポーネントが一時停止します(Suspenseによるローディング状態で説明されているように)。これは、使用しているfetchPolicyにより、ネットワークリクエストが常に作成されるためです。つまり、フォールバックのローディング状態を表示するために、MainContentコンポーネントをラップするSuspense境界があることを確認する必要があります。

Suspenseを回避する必要がある場合

場合によっては、すでにレンダリングされたコンテンツを非表示にするSuspenseフォールバックの表示を避けたい場合があります。このような場合は、代わりにfetchQueryを使用して、手動でローディング状態を追跡できます。

注意

Reactの将来のバージョンで同時レンダリングがサポートされるようになったら、Reactは、一時停止時にSuspenseフォールバックで既にレンダリングされたコンテンツを非表示にすることを回避するためのオプションを提供します。

/**
* App.react.js
*/
import type {AppQuery as AppQueryType} from 'AppQuery.graphql';

const AppQuery = require('__generated__/AppQuery.graphql');

function App(props: Props) {
const variables = {id: '4'}
const environment = useRelayEnvironment();
const [refreshedQueryOptions, setRefreshedQueryOptions] = useState(null);
const [isRefreshing, setIsRefreshing] = useState(false)

const refresh = 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.
setRefreshedQueryOptions(prev => ({
fetchKey: (prev?.fetchKey ?? 0) + 1,
fetchPolicy: 'store-only',
}));
}
error: () => {
setIsRefreshing(false);
}
});
}, [/* ... */]);

return (
<React.Suspense fallback="Loading query...">
<MainContent
isRefreshing={isRefreshing}
refresh={refresh}
queryOptions={refreshedQueryOptions ?? {}}
variables={variables}
/>
</React.Suspense>
);
}

ここで何が起こっているかを詳しく見てみましょう。

  • リフレッシュする場合、一時停止を回避しているため、独自のisRefreshingローディング状態を追跡するようになりました。この状態を使用して、MainContentを非表示にすることなくMainContentコンポーネント内にビジースピナーまたは同様のローディングUIをレンダリングできます。
  • イベントハンドラーで、最初にfetchQueryを呼び出してクエリをフェッチし、データをローカルのRelayストアに書き込みます。fetchQueryネットワークリクエストが完了すると、前の例と同様に、更新されたfetchKeyfetchPolicyを再レンダリングするように状態を更新し、それをuseLazyLoadQueryに渡して更新されたデータをレンダリングします。
  • この時点で、状態を更新すると、クエリのデータはすでにローカルのRelayストアにキャッシュされているはずなので、一時停止を回避し、キャッシュ済みのデータのみを読み取るために、'store-only'fetchPolicyを使用します。

このページは役に立ちましたか?

いくつかの簡単な質問に答えて、サイトをさらに改善にご協力ください いくつか簡単な質問にご回答ください.