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 { Treaty } from "@elysiajs/eden";
import { Effect, Fiber, pipe } from "effect";
import { useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
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 });
}
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);
useEffect(() => {
setResult(IS_LOADING);
const fiber = pipe(
effect,
Effect.match({
@@ -117,7 +119,7 @@ export function useLoadingEffect<A, E>(effect: Effect.Effect<A, E>) {
return () => {
Effect.runFork(interruptEffect);
};
}, []);
}, deps);
return result;
}

View File

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

View File

@@ -20,7 +20,7 @@ export function Piece() {
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[]>) => {
setData!(mapProp("attachments", action));