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,26 +17,25 @@ 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* () {
yield* Clock.sleep(Duration.millis(500));
const { error, data } = yield* Effect.promise((signal) => client.piece.get({
query: { query: {
...(name !== "" ? { name } : undefined), ...(name !== "" ? { name } : undefined),
...(author !== "" ? { author } : undefined), ...(author !== "" ? { author } : undefined),
}, },
}), [name, author]); 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 ? (
Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}`
) : (<>
<div className="flex flex-row gap-4"> <div className="flex flex-row gap-4">
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
@@ -71,10 +70,26 @@ export function Home() {
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{pieceIds.map((pieceId) => <PieceRow key={pieceId} pieceId={pieceId} />)} {isLoading ? (
<TableRow>
<TableCell colSpan={4} >
<div className="flex items-center justify-center gap-2">
<Loader2 className="animate-spin" />
Ładowanie
</div>
</TableCell>
</TableRow>
) : 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> </TableBody>
</Table> </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));