From 3a79ce2cf82a288f331c78382357089d41c3a69c Mon Sep 17 00:00:00 2001 From: dswbx Date: Sat, 21 Dec 2024 15:03:14 +0100 Subject: [PATCH] added a new mutate replacement for useEntityMutate to quickly update cache without revalidating --- app/src/data/entities/Mutator.ts | 2 +- app/src/ui/client/api/use-entity.ts | 51 +++++++++++++- .../ui/routes/test/tests/swr-and-data-api.tsx | 68 ++++++++++++------- 3 files changed, 91 insertions(+), 30 deletions(-) diff --git a/app/src/data/entities/Mutator.ts b/app/src/data/entities/Mutator.ts index 51a1d12..cb25ddf 100644 --- a/app/src/data/entities/Mutator.ts +++ b/app/src/data/entities/Mutator.ts @@ -270,7 +270,7 @@ export class Mutator< return (await this.many(qb)) as any; } - async updateWhere(data: Input, where?: RepoQuery["where"]): Promise> { + async updateWhere(data: Partial, where?: RepoQuery["where"]): Promise> { const entity = this.entity; const validatedData = await this.getValidatedData(data, "update"); diff --git a/app/src/ui/client/api/use-entity.ts b/app/src/ui/client/api/use-entity.ts index dd93455..89ff6e0 100644 --- a/app/src/ui/client/api/use-entity.ts +++ b/app/src/ui/client/api/use-entity.ts @@ -3,7 +3,7 @@ import { encodeSearch, objectTransform } from "core/utils"; import type { EntityData, RepoQuery } from "data"; import type { ModuleApi, ResponseObject } from "modules/ModuleApi"; import useSWR, { type SWRConfiguration, mutate } from "swr"; -import { useApi } from "ui/client"; +import { type Api, useApi } from "ui/client"; export class UseEntityApiError extends Error { constructor( @@ -148,9 +148,44 @@ export const useEntityQuery = < }; }; +export async function mutateEntityCache< + Entity extends keyof DB | string, + Data = Entity extends keyof DB ? Omit : EntityData +>(api: Api["data"], entity: Entity, id: PrimaryFieldType, partialData: Partial) { + function update(prev: any, partialNext: any) { + if ( + typeof prev !== "undefined" && + typeof partialNext !== "undefined" && + "id" in prev && + prev.id === id + ) { + return { ...prev, ...partialNext }; + } + + return prev; + } + + const entityKey = makeKey(api, entity); + + return mutate( + (key) => typeof key === "string" && key.startsWith(entityKey), + async (data) => { + if (typeof data === "undefined") return; + if (Array.isArray(data)) { + return data.map((item) => update(item, partialData)); + } + return update(data, partialData); + }, + { + revalidate: false + } + ); +} + export const useEntityMutate = < Entity extends keyof DB | string, - Id extends PrimaryFieldType | undefined = undefined + Id extends PrimaryFieldType | undefined = undefined, + Data = Entity extends keyof DB ? Omit : EntityData >( entity: Entity, id?: Id, @@ -160,5 +195,15 @@ export const useEntityMutate = < ...options, enabled: false }); - return $q; + + const _mutate = id + ? (data) => mutateEntityCache($q.api, entity, id, data) + : (id, data) => mutateEntityCache($q.api, entity, id, data); + + return { + ...$q, + mutate: _mutate as unknown as Id extends undefined + ? (id: PrimaryFieldType, data: Partial) => Promise + : (data: Partial) => Promise + }; }; diff --git a/app/src/ui/routes/test/tests/swr-and-data-api.tsx b/app/src/ui/routes/test/tests/swr-and-data-api.tsx index fe8d7b1..ebc6d59 100644 --- a/app/src/ui/routes/test/tests/swr-and-data-api.tsx +++ b/app/src/ui/routes/test/tests/swr-and-data-api.tsx @@ -1,53 +1,69 @@ import { useEffect, useState } from "react"; -import { useEntity, useEntityQuery } from "ui/client/api/use-entity"; +import { useEntity, useEntityMutate, useEntityQuery } from "ui/client/api/use-entity"; import { Scrollable } from "ui/layouts/AppShell/AppShell"; export default function SwrAndDataApi() { return ( -
+ + asdf -
+ + ); } -function QueryDataApi() { - const [text, setText] = useState(""); - const { data, update, ...r } = useEntityQuery("comments", 2, { - sort: { by: "id", dir: "desc" } +function QueryMutateDataApi() { + const { mutate } = useEntityMutate("comments"); + const { data, ...r } = useEntityQuery("comments", undefined, { + limit: 2 }); - const comment = data ? data : null; - - useEffect(() => { - setText(comment?.content ?? ""); - }, [comment]); return ( - +
+ bla
{JSON.stringify(r.key)}
{r.error &&
failed to load
} {r.isLoading &&
loading...
} {data &&
{JSON.stringify(data, null, 2)}
} {data && ( -
{ - e.preventDefault(); - if (!comment) return; - await update({ content: text }); - return false; - }} - > - setText(e.target.value)} /> - -
+
+ {data.map((comment) => ( + { + await mutate(comment.id, { content: e.target.value }); + }} + className="border border-black" + /> + ))} +
)} - +
+ ); +} + +function QueryDataApi() { + const { data, update, ...r } = useEntityQuery("comments", undefined, { + sort: { by: "id", dir: "asc" }, + limit: 3 + }); + + return ( +
+
{JSON.stringify(r.key)}
+ {r.error &&
failed to load
} + {r.isLoading &&
loading...
} + {data &&
{JSON.stringify(data, null, 2)}
} +
); } function DirectDataApi() { const [data, setData] = useState(); - const { create, read, update, _delete } = useEntity("users"); + const { create, read, update, _delete } = useEntity("comments"); useEffect(() => { read().then((data) => setData(data));