Improve loading hook, reduce query API spam

This commit is contained in:
Szymon Nowakowski
2024-12-24 14:54:23 +01:00
parent d8a1f3aa80
commit 1786e1cac9
3 changed files with 76 additions and 59 deletions

View File

@@ -1,7 +1,7 @@
import { mapProp, Updater, useStore } from "@/hooks/useStore"; import { mapProp, Updater, useStore } from "@/hooks/useStore";
import { Treaty } from "@elysiajs/eden"; import { Treaty } from "@elysiajs/eden";
import { Effect, Fiber, pipe } from "effect"; import { Effect, Fiber, pipe } from "effect";
import { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
export type LoadingResult<R extends Record<number, unknown>> = export type LoadingResult<R extends Record<number, unknown>> =
@@ -93,11 +93,13 @@ function mapFailure<E>(error: E) {
return Object.freeze({ isLoading: false as const, data: null, error }); return Object.freeze({ isLoading: false as const, data: null, error });
} }
export function useLoadingEffect<A, E>(effect: Effect.Effect<A, E>) { export function useLoadingEffect<A, E>(effect: Effect.Effect<A, E>, deps: React.DependencyList) {
const [result, setResult] = useState<LoadingEffectResult<A, E>>(IS_LOADING); const [result, setResult] = useState<LoadingEffectResult<A, E>>(IS_LOADING);
useEffect(() => { useEffect(() => {
setResult(IS_LOADING);
const fiber = pipe( const fiber = pipe(
effect, effect,
Effect.match({ Effect.match({
@@ -117,7 +119,7 @@ export function useLoadingEffect<A, E>(effect: Effect.Effect<A, E>) {
return () => { return () => {
Effect.runFork(interruptEffect); Effect.runFork(interruptEffect);
}; };
}, []); }, deps);
return result; return result;
} }

View File

@@ -5,9 +5,9 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogT
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useLoading, useLoadingEffect } from "@/hooks/useLoading"; import { useLoadingEffect } from "@/hooks/useLoading";
import { PieceId } from "common"; import { PieceId } from "common";
import { Cause, Effect, Option } from "effect"; import { Cause, Clock, Duration, Effect, Option } from "effect";
import { Loader2, Plus } from "lucide-react"; import { Loader2, Plus } from "lucide-react";
import { FormEventHandler, ReactNode, useId, useState } from "react"; import { FormEventHandler, ReactNode, useId, useState } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
@@ -17,64 +17,79 @@ export function Home() {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [author, setAuthor] = useState(""); const [author, setAuthor] = useState("");
const { isLoading, error, data: pieceIds } = useLoading(() => client.piece.get({ const { isLoading, error, data: pieceIds } = useLoadingEffect(Effect.gen(function* () {
query: { yield* Clock.sleep(Duration.millis(500));
...(name !== "" ? { name } : undefined), const { error, data } = yield* Effect.promise((signal) => client.piece.get({
...(author !== "" ? { author } : undefined), query: {
}, ...(name !== "" ? { name } : undefined),
}), [name, author]); ...(author !== "" ? { author } : undefined),
},
fetch: { signal },
}));
if (isLoading) { if (error !== null) {
return ( return yield* Effect.fail(error);
<div className="w-full h-full overflow-hidden flex items-center justify-center"> } else {
<div>Ładowanie</div> return data;
</div> }
); }), [name, author]);
}
return ( return (
<div className="p-4 overflow-y-auto flex flex-col items-start gap-4"> <div className="p-4 overflow-y-auto flex flex-col items-start gap-4">
{error !== null ? ( <div className="flex flex-row gap-4">
Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}` <Dialog>
) : (<> <DialogTrigger asChild>
<div className="flex flex-row gap-4"> <Button variant="outline">
<Dialog> <Plus />Dodaj utwór
<DialogTrigger asChild> </Button>
<Button variant="outline"> </DialogTrigger>
<Plus />Dodaj utwór <AddPieceDialogContent />
</Button> </Dialog>
</DialogTrigger> <Input
<AddPieceDialogContent /> className="w-[32ch]"
</Dialog> type="text"
<Input placeholder="Tytuł"
className="w-[32ch]" value={name}
type="text" onChange={(e) => setName(e.target.value)}
placeholder="Tytuł" />
value={name} <Input
onChange={(e) => setName(e.target.value)} className="w-[32ch]"
/> type="text"
<Input placeholder="Autor"
className="w-[32ch]" value={author}
type="text" onChange={(e) => setAuthor(e.target.value)}
placeholder="Autor" />
value={author} </div>
onChange={(e) => setAuthor(e.target.value)} <Table>
/> <TableHeader>
</div> <TableRow>
<Table> <TableHead>Tytuł</TableHead>
<TableHeader> <TableHead>Twórcy</TableHead>
<TableHead className="text-center">Dodano</TableHead>
<TableHead className="text-center">Zmodyfikowano</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow> <TableRow>
<TableHead>Tytuł</TableHead> <TableCell colSpan={4} >
<TableHead>Twórcy</TableHead> <div className="flex items-center justify-center gap-2">
<TableHead className="text-center">Dodano</TableHead> <Loader2 className="animate-spin" />
<TableHead className="text-center">Zmodyfikowano</TableHead> Ładowanie
</div>
</TableCell>
</TableRow> </TableRow>
</TableHeader> ) : error !== null ? (
<TableBody> <TableRow>
{pieceIds.map((pieceId) => <PieceRow key={pieceId} pieceId={pieceId} />)} <TableCell colSpan={4} className="text-center">
</TableBody> {Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}`}
</Table> </TableCell>
</>)} </TableRow>
) : (
pieceIds.map((pieceId) => <PieceRow key={pieceId} pieceId={pieceId} />)
)}
</TableBody>
</Table>
</div> </div>
); );
} }
@@ -87,7 +102,7 @@ namespace PieceRow {
function PieceRow(props: PieceRow.Props) { function PieceRow(props: PieceRow.Props) {
const { isLoading, error, data: piece } = useLoadingEffect(Effect.uninterruptible(pieceCache.get(props.pieceId))); const { isLoading, error, data: piece } = useLoadingEffect(Effect.uninterruptible(pieceCache.get(props.pieceId)), [props.pieceId]);
if (isLoading) { if (isLoading) {
return ( return (

View File

@@ -20,7 +20,7 @@ export function Piece() {
const id = PieceId(useParams().pieceId!); const id = PieceId(useParams().pieceId!);
const { isLoading, error, data, setData } = useLoadingEffect(Effect.uninterruptible(pieceCache.get(id))); const { isLoading, error, data, setData } = useLoadingEffect(Effect.uninterruptible(pieceCache.get(id)), [id]);
const setAttachments = useCallback((action: Update<readonly Attachment[]>) => { const setAttachments = useCallback((action: Update<readonly Attachment[]>) => {
setData!(mapProp("attachments", action)); setData!(mapProp("attachments", action));