Name:
interface
Value:
Amplify has re-imagined the way frontend developers build fullstack applications. Develop and deploy without the hassle.
Gen1 DocsLegacy

Page updated Mar 26, 2026

オプティミスティックUI

Amplify DataはTanStack Queryと一緒に使用して、オプティミスティックUIを実装できます。これにより、CRUD操作がリクエストのラウンドトリップが完了する前に、UIに即座にレンダリングされます。Amplify DataをTanStackと一緒に使用すると、ローディング状態とエラー状態を簡単にレンダリングでき、APIコールが失敗した場合はUIの変更をロールバックできます。

以下の例では、新しく作成されたアイテムをオプティミスティックにレンダリングするリストビューと、更新と削除をオプティミスティックにレンダリングするディテールビューを作成します。

TanStack Queryの詳細、サポートされているブラウザ、高度な使用方法については、TanStack Query ドキュメントを参照してください。 TanStack Queryでオプティミスティック更新を実装する方法に関する完全なガイダンスについては、TanStack Query オプティミスティックUI ドキュメントを参照してください。 Amplify Dataの詳細については、API ドキュメントを参照してください。

開始するには、Reactフロントエンドを持つ既存のAmplifyプロジェクトで以下のコマンドを実行します:

Terminal
npm add @tanstack/react-query && \
npm add --save-dev @tanstack/react-query-devtools

Dataスキーマを修正して、この「Real Estate Property」の例を使用します:

amplify/data/resource.ts
const schema = a.schema({
RealEstateProperty: a.model({
name: a.string().required(),
address: a.string(),
}).authorization(allow => [allow.guest()])
})
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'iam',
},
});

ファイルを保存し、npx ampx sandboxを実行してバックエンドのクラウドサンドボックスに変更をデプロイします。このガイドでは、Real Estate Propertyリスティングアプリケーションを構築します。

次に、プロジェクトのルートで、必要なTanStack Queryインポートを追加し、クライアントを作成します:

src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { Amplify } from 'aws-amplify'
import outputs from '../amplify_outputs.json'
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
Amplify.configure(outputs)
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>,
)

TanStack Query Devtoolsは必須ではありませんが、TanStackがどのように機能するかをデバッグして理解するのに便利です。デフォルトでは、React Query Devtoolsはprocess.env.NODE_ENV === 'development'の場合にのみバンドルに含まれるため、本番ビルドから除外するために追加の設定は必要ありません。 TanStack Query Devtoolsの詳細については、TanStack Query Devtools ドキュメントを参照してください。

完全な動作例(必要なインポートとReactコンポーネント状態管理を含む)については、下の完全な例を参照してください。

Amplify Data APIでTanStack Query クエリキーを使用する方法

TanStack Queryは、指定したクエリキーに基づいてクエリキャッシュを管理します。クエリキーは配列である必要があります。配列には単一の文字列、または複数の文字列とネストされたオブジェクトを含めることができます。クエリキーはシリアライズ可能で、クエリのデータに一意である必要があります。

TanStackを使用してAmplify DataでオプティミスティックUIをレンダリングする場合、APIの操作に応じて異なるクエリキーを使用する必要があります。アイテムのリストを取得する場合は、単一の文字列が使用されます(例: queryKey: ["realEstateProperties"])。このクエリキーは、新しく作成されたアイテムをオプティミスティックにレンダリングする場合にも使用されます。アイテムを更新または削除する場合、クエリキーには削除または更新されるレコードの一意の識別子も含める必要があります(例: queryKey: ["realEstateProperties", newRealEstateProperty.id"])。

クエリキーの詳細については、TanStack Queryドキュメントを参照してください。

レコードのリストをオプティミスティックにレンダリングする

Amplify Data APIから返されたアイテムのリストをオプティミスティックにレンダリングするには、TanStackのuseQueryフックを使用して、Data APIクエリをqueryFnパラメータとして渡します。次の例は、APIからすべてのレコードを取得するクエリを作成します。クエリキーとしてrealEstatePropertiesを使用します。これは、新しく作成されたアイテムをオプティミスティックにレンダリングする場合に使用する同じキーです。

src/App.tsx
import type { Schema } from '../amplify/data/resource'
import { generateClient } from 'aws-amplify/data'
import { useQuery } from '@tanstack/react-query'
const client = generateClient<Schema>();
function App() {
const {
data: realEstateProperties,
isLoading,
isSuccess,
isError: isErrorQuery,
} = useQuery({
queryKey: ["realEstateProperties"],
queryFn: async () => {
const response = await client.models.RealEstateProperty.list();
const allRealEstateProperties = response.data;
if (!allRealEstateProperties) return null;
return allRealEstateProperties;
},
});
// return ...
}

新しく作成されたレコードをオプティミスティックにレンダリングする

Amplify Data APIから返された新しく作成されたレコードをオプティミスティックにレンダリングするには、TanStackのuseMutationフックを使用して、Amplify Data API 変更をラッパーのmutationFnパラメータとして渡します。useQueryフックで使用されたものと同じクエリキー(realEstateProperties)を、新しく作成されたアイテムをオプティミスティックにレンダリングするためのクエリキーとして使用します。 onMutate関数を使用してキャッシュを直接更新し、onError関数を使用してリクエストが失敗した場合の変更をロールバックします。

import { generateClient } from 'aws-amplify/api'
import type { Schema } from '../amplify/data/resource'
import { useQueryClient, useMutation } from '@tanstack/react-query'
const client = generateClient<Schema>()
function App() {
const queryClient = useQueryClient();
const createMutation = useMutation({
mutationFn: async (input: { name: string, address: string }) => {
const { data: newRealEstateProperty } = await client.models.RealEstateProperty.create(input)
return newRealEstateProperty;
},
// mutateが呼び出されたとき:
onMutate: async (newRealEstateProperty) => {
// 発信中のrefetchをキャンセルする
// (オプティミスティック更新を上書きしないようにするため)
await queryClient.cancelQueries({ queryKey: ["realEstateProperties"] });
// 前の値をスナップショット
const previousRealEstateProperties = queryClient.getQueryData([
"realEstateProperties",
]);
// オプティミスティックに新しい値に更新
if (previousRealEstateProperties) {
queryClient.setQueryData(["realEstateProperties"], (old: Schema["RealEstateProperty"]["type"][]) => [
...old,
newRealEstateProperty,
]);
}
// スナップショット値を持つコンテキストオブジェクトを返す
return { previousRealEstateProperties };
},
// 変更が失敗した場合、
// onMutateから返されたコンテキストを使用してロールバック
onError: (err, newRealEstateProperty, context) => {
console.error("Error saving record:", err, newRealEstateProperty);
if (context?.previousRealEstateProperties) {
queryClient.setQueryData(
["realEstateProperties"],
context.previousRealEstateProperties
);
}
},
// エラーまたは成功後は常にrefetch:
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["realEstateProperties"] });
},
});
// return ...
}

TanStack Queryで単一のアイテムをクエリする

単一のアイテムの更新をオプティミスティックにレンダリングするために、まずAPIからアイテムを取得します。useQueryフックを使用して、getクエリをqueryFnパラメータとして渡します。クエリキーについては、realEstatePropertiesとレコードの一意の識別子の組み合わせを使用します。

import { generateClient } from 'aws-amplify/data'
import type { Schema } from '../amplify/data/resource'
import { useQuery } from '@tanstack/react-query'
const client = generateClient<Schema>()
function App() {
const currentRealEstatePropertyId = "SOME_ID"
const {
data: realEstateProperty,
isLoading,
isSuccess,
isError: isErrorQuery,
} = useQuery({
queryKey: ["realEstateProperties", currentRealEstatePropertyId],
queryFn: async () => {
if (!currentRealEstatePropertyId) { return }
const { data: property } = await client.models.RealEstateProperty.get({
id: currentRealEstatePropertyId,
});
return property;
},
});
}

レコードの更新をオプティミスティックにレンダリングする

単一のレコードに対するAmplify Data更新をオプティミスティックにレンダリングするには、TanStackのuseMutationフックを使用して、更新変更をmutationFnパラメータとして渡します。単一レコードuseQueryフックで使用されたのと同じクエリキーの組み合わせ(realEstatePropertiesとレコードのid)を、更新をオプティミスティックにレンダリングするためのクエリキーとして使用します。 onMutate関数を使用してキャッシュを直接更新し、onError関数を使用してリクエストが失敗した場合の変更をロールバックします。

onMutate関数を使用してキャッシュと直接対話する場合、newRealEstatePropertyパラメータは更新されているフィールドのみを含みます。setQueryDataを呼び出す場合、更新されたフィールドのみのオプティミスティック値をUIでレンダリングするのを避けるために、すべてのフィールドの以前の値を新しく更新されたフィールドと一緒に含めてください。

src/App.tsx
import { generateClient } from 'aws-amplify/data'
import type { Schema } from '../amplify/data/resource'
import { useQueryClient, useMutation } from "@tanstack/react-query";
const client = generateClient<Schema>()
function App() {
const queryClient = useQueryClient();
const updateMutation = useMutation({
mutationFn: async (realEstatePropertyDetails: { id: string, name?: string, address?: string }) => {
const { data: updatedProperty } = await client.models.RealEstateProperty.update(realEstatePropertyDetails);
return updatedProperty;
},
// mutateが呼び出されたとき:
onMutate: async (newRealEstateProperty: { id: string, name?: string, address?: string }) => {
// 発信中のrefetchをキャンセルする
// (オプティミスティック更新を上書きしないようにするため)
await queryClient.cancelQueries({
queryKey: ["realEstateProperties", newRealEstateProperty.id],
});
await queryClient.cancelQueries({
queryKey: ["realEstateProperties"],
});
// 前の値をスナップショット
const previousRealEstateProperty = queryClient.getQueryData([
"realEstateProperties",
newRealEstateProperty.id,
]);
// オプティミスティックに新しい値に更新
if (previousRealEstateProperty) {
queryClient.setQueryData(
["realEstateProperties", newRealEstateProperty.id],
/**
* `newRealEstateProperty`は最初、レコードの更新された値のみを含みます。
* UIで更新されたフィールドのみのオプティミスティック値をレンダリング
* するのを避けるために、すべてのフィールドの以前の値を含めてください:
*/
{ ...previousRealEstateProperty, ...newRealEstateProperty }
);
}
// 前と新しいrealEstatePropertyを含むコンテキストを返す
return { previousRealEstateProperty, newRealEstateProperty };
},
// 変更が失敗した場合、上で返されたコンテキストを使用
onError: (err, newRealEstateProperty, context) => {
console.error("Error updating record:", err, newRealEstateProperty);
if (context?.previousRealEstateProperty) {
queryClient.setQueryData(
["realEstateProperties", context.newRealEstateProperty.id],
context.previousRealEstateProperty
);
}
},
// エラーまたは成功後は常にrefetch:
onSettled: (newRealEstateProperty) => {
if (newRealEstateProperty) {
queryClient.invalidateQueries({
queryKey: ["realEstateProperties", newRealEstateProperty.id],
});
queryClient.invalidateQueries({
queryKey: ["realEstateProperties"],
});
}
},
});
}

レコードの削除をオプティミスティックにレンダリングする

単一のレコードの削除をオプティミスティックにレンダリングするには、TanStackのuseMutationフックを使用して、削除変更をmutationFnパラメータとして渡します。単一レコードuseQueryフックで使用されたのと同じクエリキーの組み合わせ(realEstatePropertiesとレコードのid)を、更新をオプティミスティックにレンダリングするためのクエリキーとして使用します。 onMutate関数を使用してキャッシュを直接更新し、onError関数を使用して削除が失敗した場合の変更をロールバックします。

src/App.tsx
import { generateClient } from 'aws-amplify/data'
import type { Schema } from '../amplify/data/resource'
import { useQueryClient, useMutation } from '@tanstack/react-query'
const client = generateClient<Schema>()
function App() {
const queryClient = useQueryClient();
const deleteMutation = useMutation({
mutationFn: async (realEstatePropertyDetails: { id: string }) => {
const { data: deletedProperty } = await client.models.RealEstateProperty.delete(realEstatePropertyDetails);
return deletedProperty;
},
// mutateが呼び出されたとき:
onMutate: async (newRealEstateProperty) => {
// 発信中のrefetchをキャンセルする
// (オプティミスティック更新を上書きしないようにするため)
await queryClient.cancelQueries({
queryKey: ["realEstateProperties", newRealEstateProperty.id],
});
await queryClient.cancelQueries({
queryKey: ["realEstateProperties"],
});
// 前の値をスナップショット
const previousRealEstateProperty = queryClient.getQueryData([
"realEstateProperties",
newRealEstateProperty.id,
]);
// オプティミスティックに新しい値に更新
if (previousRealEstateProperty) {
queryClient.setQueryData(
["realEstateProperties", newRealEstateProperty.id],
newRealEstateProperty
);
}
// 前と新しいrealEstatePropertyを含むコンテキストを返す
return { previousRealEstateProperty, newRealEstateProperty };
},
// 変更が失敗した場合、上で返されたコンテキストを使用
onError: (err, newRealEstateProperty, context) => {
console.error("Error deleting record:", err, newRealEstateProperty);
if (context?.previousRealEstateProperty) {
queryClient.setQueryData(
["realEstateProperties", context.newRealEstateProperty.id],
context.previousRealEstateProperty
);
}
},
// エラーまたは成功後は常にrefetch:
onSettled: (newRealEstateProperty) => {
if (newRealEstateProperty) {
queryClient.invalidateQueries({
queryKey: ["realEstateProperties", newRealEstateProperty.id],
});
queryClient.invalidateQueries({
queryKey: ["realEstateProperties"],
});
}
},
});
}

オプティミスティックレンダリングされたデータのローディング状態とエラー状態

useQueryuseMutationの両方は、クエリまたは変更の現在の状態を示すisLoadingisErrorの状態を返します。これらの状態を使用して、ローディングとエラーインジケータをレンダリングできます。

操作固有のローディング状態に加えて、TanStack QueryはuseIsFetchingフックを提供します。このデモの目的のため、完全な例では、バックグラウンドでのフェッチを含む任意のクエリがフェッチされている場合に、TanStackがバックグラウンドで何をしているかを視覚化するのに役立つグローバルローディングインジケータを表示しています:

function GlobalLoadingIndicator() {
const isFetching = useIsFetching();
return isFetching ? <div style={styles.globalLoadingIndicator}></div> : null;
}

TanStack Queryフックの高度な使用方法の詳細については、TanStackドキュメントを参照してください。

次の例は、TanStackから返された状態を使用して、変更進行中のローディングインジケータとエラーメッセージをレンダリングする方法を示しています。その他の例については、下の完全な例を参照してください。

<>
{updateMutation.isError &&
updateMutation.error instanceof Error ? (
<div>An error occurred: {updateMutation.error.message}</div>
) : null}
{updateMutation.isSuccess ? (
<div>Real Estate Property updated!</div>
) : null}
<button
onClick={() =>
updateMutation.mutate({
id: realEstateProperty.id,
address: `${Math.floor(
1000 + Math.random() * 9000
)} Main St`,
})
}
>
Update Address
</button>
</>

完全な例

src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { Amplify } from 'aws-amplify'
import outputs from '../amplify_outputs.json'
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
Amplify.configure(outputs)
export const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</React.StrictMode>,
)
src/App.tsx
import { generateClient } from 'aws-amplify/data'
import type { Schema } from '../amplify/data/resource'
import './App.css'
import { useIsFetching, useMutation, useQuery } from '@tanstack/react-query'
import { queryClient } from './main'
import { useState } from 'react'
const client = generateClient<Schema>({
authMode: 'iam'
})
function GlobalLoadingIndicator() {
const isFetching = useIsFetching();
return isFetching ? <div style={styles.globalLoadingIndicator}></div> : null;
}
function App() {
const [currentRealEstatePropertyId, setCurrentRealEstatePropertyId] =
useState<string | null>(null);
const {
data: realEstateProperties,
isLoading,
isSuccess,
isError: isErrorQuery,
} = useQuery({
queryKey: ["realEstateProperties"],
queryFn: async () => {
const response = await client.models.RealEstateProperty.list();
const allRealEstateProperties = response.data;
if (!allRealEstateProperties) return null;
return allRealEstateProperties;
},
});
const createMutation = useMutation({
mutationFn: async (input: { name: string, address: string }) => {
const { data: newRealEstateProperty } = await client.models.RealEstateProperty.create(input)
return newRealEstateProperty;
},
// mutateが呼び出されたとき:
onMutate: async (newRealEstateProperty) => {
// 発信中のrefetchをキャンセルする
// (オプティミスティック更新を上書きしないようにするため)
await queryClient.cancelQueries({ queryKey: ["realEstateProperties"] });
// 前の値をスナップショット
const previousRealEstateProperties = queryClient.getQueryData([
"realEstateProperties",
]);
// オプティミスティックに新しい値に更新
if (previousRealEstateProperties) {
queryClient.setQueryData(["realEstateProperties"], (old: Schema["RealEstateProperty"]["type"][]) => [
...old,
newRealEstateProperty,
]);
}
// スナップショット値を持つコンテキストオブジェクトを返す
return { previousRealEstateProperties };
},
// 変更が失敗した場合、
// onMutateから返されたコンテキストを使用してロールバック
onError: (err, newRealEstateProperty, context) => {
console.error("Error saving record:", err, newRealEstateProperty);
if (context?.previousRealEstateProperties) {
queryClient.setQueryData(
["realEstateProperties"],
context.previousRealEstateProperties
);
}
},
// エラーまたは成功後は常にrefetch:
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["realEstateProperties"] });
},
});
function RealEstatePropertyDetailView() {
const {
data: realEstateProperty,
isLoading,
isSuccess,
isError: isErrorQuery,
} = useQuery({
queryKey: ["realEstateProperties", currentRealEstatePropertyId],
queryFn: async () => {
if (!currentRealEstatePropertyId) { return }
const { data: property } = await client.models.RealEstateProperty.get({ id: currentRealEstatePropertyId });
return property
},
});
const updateMutation = useMutation({
mutationFn: async (realEstatePropertyDetails: { id: string, name?: string, address?: string }) => {
const { data: updatedProperty } = await client.models.RealEstateProperty.update(realEstatePropertyDetails);
return updatedProperty;
},
// mutateが呼び出されたとき:
onMutate: async (newRealEstateProperty: { id: string, name?: string, address?: string }) => {
// 発信中のrefetchをキャンセルする
// (オプティミスティック更新を上書きしないようにするため)
await queryClient.cancelQueries({
queryKey: ["realEstateProperties", newRealEstateProperty.id],
});
await queryClient.cancelQueries({
queryKey: ["realEstateProperties"],
});
// 前の値をスナップショット
const previousRealEstateProperty = queryClient.getQueryData([
"realEstateProperties",
newRealEstateProperty.id,
]);
// オプティミスティックに新しい値に更新
if (previousRealEstateProperty) {
queryClient.setQueryData(
["realEstateProperties", newRealEstateProperty.id],
/**
* `newRealEstateProperty`は最初、レコードの更新された値のみを含みます。
* UIで更新されたフィールドのみのオプティミスティック値をレンダリング
* するのを避けるために、すべてのフィールドの以前の値を含めてください:
*/
{ ...previousRealEstateProperty, ...newRealEstateProperty }
);
}
// 前と新しいrealEstatePropertyを含むコンテキストを返す
return { previousRealEstateProperty, newRealEstateProperty };
},
// 変更が失敗した場合、上で返されたコンテキストを使用
onError: (err, newRealEstateProperty, context) => {
console.error("Error updating record:", err, newRealEstateProperty);
if (context?.previousRealEstateProperty) {
queryClient.setQueryData(
["realEstateProperties", context.newRealEstateProperty.id],
context.previousRealEstateProperty
);
}
},
// エラーまたは成功後は常にrefetch:
onSettled: (newRealEstateProperty) => {
if (newRealEstateProperty) {
queryClient.invalidateQueries({
queryKey: ["realEstateProperties", newRealEstateProperty.id],
});
queryClient.invalidateQueries({
queryKey: ["realEstateProperties"],
});
}
},
});
const deleteMutation = useMutation({
mutationFn: async (realEstatePropertyDetails: { id: string }) => {
const { data: deletedProperty } = await client.models.RealEstateProperty.delete(realEstatePropertyDetails);
return deletedProperty;
},
// mutateが呼び出されたとき:
onMutate: async (newRealEstateProperty) => {
// 発信中のrefetchをキャンセルする
// (オプティミスティック更新を上書きしないようにするため)
await queryClient.cancelQueries({
queryKey: ["realEstateProperties", newRealEstateProperty.id],
});
await queryClient.cancelQueries({
queryKey: ["realEstateProperties"],
});
// 前の値をスナップショット
const previousRealEstateProperty = queryClient.getQueryData([
"realEstateProperties",
newRealEstateProperty.id,
]);
// オプティミスティックに新しい値に更新
if (previousRealEstateProperty) {
queryClient.setQueryData(
["realEstateProperties", newRealEstateProperty.id],
newRealEstateProperty
);
}
// 前と新しいrealEstatePropertyを含むコンテキストを返す
return { previousRealEstateProperty, newRealEstateProperty };
},
// 変更が失敗した場合、上で返されたコンテキストを使用
onError: (err, newRealEstateProperty, context) => {
console.error("Error deleting record:", err, newRealEstateProperty);
if (context?.previousRealEstateProperty) {
queryClient.setQueryData(
["realEstateProperties", context.newRealEstateProperty.id],
context.previousRealEstateProperty
);
}
},
// エラーまたは成功後は常にrefetch:
onSettled: (newRealEstateProperty) => {
if (newRealEstateProperty) {
queryClient.invalidateQueries({
queryKey: ["realEstateProperties", newRealEstateProperty.id],
});
queryClient.invalidateQueries({
queryKey: ["realEstateProperties"],
});
}
},
});
return (
<div style={styles.detailViewContainer}>
<h2>Real Estate Property Detail View</h2>
{isErrorQuery && <div>{"Problem loading Real Estate Property"}</div>}
{isLoading && (
<div style={styles.loadingIndicator}>
{"Loading Real Estate Property..."}
</div>
)}
{isSuccess && (
<div>
<p>{`Name: ${realEstateProperty?.name}`}</p>
<p>{`Address: ${realEstateProperty?.address}`}</p>
</div>
)}
{realEstateProperty && (
<div>
<div>
{updateMutation.isPending ? (
"Updating Real Estate Property..."
) : (
<>
{updateMutation.isError &&
updateMutation.error instanceof Error ? (
<div>An error occurred: {updateMutation.error.message}</div>
) : null}
{updateMutation.isSuccess ? (
<div>Real Estate Property updated!</div>
) : null}
<button
onClick={() =>
updateMutation.mutate({
id: realEstateProperty.id,
name: `Updated Home ${Date.now()}`,
})
}
>
Update Name
</button>
<button
onClick={() =>
updateMutation.mutate({
id: realEstateProperty.id,
address: `${Math.floor(
1000 + Math.random() * 9000
)} Main St`,
})
}
>
Update Address
</button>
</>
)}
</div>
<div>
{deleteMutation.isPending ? (
"Deleting Real Estate Property..."
) : (
<>
{deleteMutation.isError &&
deleteMutation.error instanceof Error ? (
<div>An error occurred: {deleteMutation.error.message}</div>
) : null}
{deleteMutation.isSuccess ? (
<div>Real Estate Property deleted!</div>
) : null}
<button
onClick={() =>
deleteMutation.mutate({
id: realEstateProperty.id,
})
}
>
Delete
</button>
</>
)}
</div>
</div>
)}
<button onClick={() => setCurrentRealEstatePropertyId(null)}>
Back
</button>
</div>
);
}
return (
<div>
{!currentRealEstatePropertyId && (
<div style={styles.appContainer}>
<h1>Real Estate Properties:</h1>
<div>
{createMutation.isPending ? (
"Adding Real Estate Property..."
) : (
<>
{createMutation.isError &&
createMutation.error instanceof Error ? (
<div>An error occurred: {createMutation.error.message}</div>
) : null}
{createMutation.isSuccess ? (
<div>Real Estate Property added!</div>
) : null}
<button
onClick={() => {
createMutation.mutate({
name: `New Home ${Date.now()}`,
address: `${Math.floor(
1000 + Math.random() * 9000
)} Main St`,
});
}}
>
Add RealEstateProperty
</button>
</>
)}
</div>
<ul style={styles.propertiesList}>
{isLoading && (
<div style={styles.loadingIndicator}>
{"Loading Real Estate Properties..."}
</div>
)}
{isErrorQuery && (
<div>{"Problem loading Real Estate Properties"}</div>
)}
{isSuccess &&
realEstateProperties?.map((realEstateProperty, idx) => {
if (!realEstateProperty) return null;
return (
<li
style={styles.listItem}
key={`${idx}-${realEstateProperty.id}`}
>
<p>{realEstateProperty.name}</p>
<button
style={styles.detailViewButton}
onClick={() =>
setCurrentRealEstatePropertyId(realEstateProperty.id)
}
>
Detail View
</button>
</li>
);
})}
</ul>
</div>
)}
{currentRealEstatePropertyId && <RealEstatePropertyDetailView />}
<GlobalLoadingIndicator />
</div>
);
}
export default App
const styles = {
appContainer: {
display: "flex",
flexDirection: "column",
alignItems: "center",
},
detailViewButton: { marginLeft: "1rem" },
detailViewContainer: { border: "1px solid black", padding: "3rem" },
globalLoadingIndicator: {
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
border: "4px solid blue",
pointerEvents: "none",
},
listItem: {
display: "flex",
justifyContent: "space-between",
border: "1px dotted grey",
padding: ".5rem",
margin: ".1rem",
},
loadingIndicator: {
border: "1px solid black",
padding: "1rem",
margin: "1rem",
},
propertiesList: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "start",
width: "50%",
border: "1px solid black",
padding: "1rem",
listStyleType: "none",
},
} as const;