Improve loading hook, reduce query API spam
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
Reference in New Issue
Block a user