Skip to content

Commit

Permalink
feat: mutation /w optimistic ui
Browse files Browse the repository at this point in the history
  • Loading branch information
patrikzita committed Jan 17, 2024
1 parent 6410c11 commit b4e521c
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 944 deletions.
906 changes: 32 additions & 874 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"drizzle-kit": "^0.20.7",
"drizzle-kit": "^0.20.13",
"eslint": "^8.56.0",
"eslint-config-next": "14.0.4",
"eslint-plugin-react": "^7.33.2",
Expand Down
Binary file modified sqlite.db
Binary file not shown.
16 changes: 0 additions & 16 deletions src/components/AnotherChildren.tsx

This file was deleted.

31 changes: 0 additions & 31 deletions src/components/Example.tsx

This file was deleted.

6 changes: 6 additions & 0 deletions src/components/layouts/MainNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const ROUTES = {
OFFSET_PAGINATION_EXPANDED: "/offset-pagination-expanded",
CREATE_DEAL: "/create-deal",
SCROLL_OFFSET_PAGINATION: "/scroll-offset-pagination",
OPTIMISTIC_UI: "/optimistic-ui",
};

const MainNav = () => {
Expand All @@ -37,6 +38,11 @@ const MainNav = () => {
Mutation - update cache
</NavigationMenuLink>
</Link>
<Link href={ROUTES.OPTIMISTIC_UI} legacyBehavior passHref>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Mutation - Optimistic UI
</NavigationMenuLink>
</Link>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
Expand Down
2 changes: 2 additions & 0 deletions src/graphql/schema/deals/deals.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class DealResolver {
.values({ title, description, isActive })
.returning();

await new Promise((resolve) => setTimeout(resolve, 5000));

return newDeal[0];
}

Expand Down
23 changes: 1 addition & 22 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
import Example from "@/components/Example";
import { GetUsersDocument, useGetUsersQuery } from "@/generated/graphql";
import { addApolloState, initializeApollo } from "@/utils/apolloClient";
import { Inter } from "next/font/google";

const inter = Inter({ subsets: ["latin"] });

export default function Home() {
const { data, refetch } = useGetUsersQuery();

return (
<main
className={`flex min-h-screen flex-col items-center justify-between p-24 ${inter.className}`}
>
<div>
<button onClick={() => refetch()}>Refetch dogs from page</button>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
<Example />
<h1 className="font-bold">Apollo Client - Examples</h1>
</main>
);
}

export async function getServerSideProps() {
const apolloClient = initializeApollo();

await apolloClient.query({
query: GetUsersDocument,
});

return addApolloState(apolloClient, {
props: {},
});
}
212 changes: 212 additions & 0 deletions src/pages/optimistic-ui/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import ExampleExplanation from "@/components/ExampleExplanation";
import { Button } from "@/components/ui/button";
import { zodResolver } from "@hookform/resolvers/zod";
import { Loader } from "lucide-react";
import { useForm } from "react-hook-form";
import * as z from "zod";

import { Checkbox } from "@/components/ui/checkbox";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import {
DealFragmentDoc,
GetDealsDocument,
useCreateDealMutation,
useGetDealsQuery,
} from "@/generated/graphql";
import { cn } from "@/lib/utils";

const EXPLANATION = {
title: "Mutation - Optimistic UI",
description:
"Nastavení mutace, tak aby dočasně nastavila data do Cache, dokud se nevrátí opravdová data.",
};

export default function OptimisticUIPage() {
return (
<main className="max-w-5xl mx-auto px-3">
<ExampleExplanation
title={EXPLANATION.title}
description={EXPLANATION.description}
>
<div className="py-1">
<h3 className="font-semibold">Poznámky</h3>
<ul className="text-xs space-y-2">
<li>
- je potřeba nastavit v mutaci optimisticResponse kam se uloží
dočasný výsledek mutace
</li>
<li>
- aby se to projevilo je potřeba nastavit zase přímý update Cache
pro určitý fragment.
</li>
</ul>
</div>
</ExampleExplanation>

<CreateDealForm />
<ShowDeals />
</main>
);
}

const DealFormSchema = z.object({
title: z.string().min(2, {
message: "Deal title must be at least 2 characters.",
}),
description: z.string().min(2, {
message: "Deal description must be at least 2 characters.",
}),
isActive: z.boolean().default(false).optional(),
});

type DealInputs = z.infer<typeof DealFormSchema>;

const CreateDealForm = () => {
const form = useForm<DealInputs>({
resolver: zodResolver(DealFormSchema),
defaultValues: {
title: "",
description: "",
isActive: false,
},
});

const [mutation, { loading }] = useCreateDealMutation({
update: (cache, { data: { createDeal } }) => {
cache.modify({
fields: {
deals(existingDeals = []) {
const newDealsRef = cache.writeFragment({
data: createDeal,
fragment: DealFragmentDoc,
});
return existingDeals.concat(newDealsRef);
},
},
});
},
});

function onSubmit(data: DealInputs) {
mutation({
variables: {
title: data.title,
description: data.description,
isActive: data.isActive,
},
optimisticResponse: {
__typename: "Mutation",
createDeal: {
__typename: "Deal",
id: "80",
title: `${data.title} (Zachvíli zmizím)`,
description: data.description,
isActive: data.isActive,
},
},
});
form.reset();
}

return (
<>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="w-2/3 space-y-6"
>
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input placeholder="Deal title" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Input placeholder="Deal description" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="isActive"
render={({ field }) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>isActive</FormLabel>
</div>
</FormItem>
)}
/>
<Button type="submit" disabled={loading}>
{loading && <Loader className="h-4 w-4 animate-spin mr-2" />}
Submit
</Button>
</form>
</Form>
</>
);
};

const ShowDeals = () => {
const { data, loading, error } = useGetDealsQuery({
fetchPolicy: "cache-first",
});

if (data?.deals.length)
return (
<div className="py-8">
{data.deals &&
data.deals.map((deal) => (
<div key={deal.id}>
<h1>
{deal.title}
<span
className={cn("text-sm text-green-400", {
"text-red-500": !deal.isActive,
})}
>
({deal?.isActive ? "true" : "false"})
</span>
</h1>
</div>
))}
</div>
);

if (loading) {
return <div>Loading...</div>;
}

if (error) {
return <div>Error..</div>;
}
};

0 comments on commit b4e521c

Please sign in to comment.