From 80ad700c4c1354f08b8776715e5304b2d2d5d452 Mon Sep 17 00:00:00 2001 From: Julien Oculi Date: Wed, 10 Jul 2024 14:46:01 +0200 Subject: [PATCH] refactor(island): :recycle: extract universal `CardList` from specific `BlogCardList` --- islands/BlogCardList.tsx | 102 ++++++++++----------------------------- islands/CardList.tsx | 102 +++++++++++++++++++++++++++++++++++++++ islands/Suspens.tsx | 2 +- routes/index.tsx | 2 +- 4 files changed, 129 insertions(+), 79 deletions(-) create mode 100644 islands/CardList.tsx diff --git a/islands/BlogCardList.tsx b/islands/BlogCardList.tsx index 2165457..9c69f04 100644 --- a/islands/BlogCardList.tsx +++ b/islands/BlogCardList.tsx @@ -1,75 +1,38 @@ import { BlogCard, BlogProps } from ':components/BlogBlocks.tsx' -import Suspense from ':islands/Suspens.tsx' -import { requestApiStream } from ':src/utils.ts' -import { Signal, useSignal } from '@preact/signals' -import type { JSX, Ref } from 'preact' -import { useEffect, useRef } from 'preact/hooks' - -function fillList( - list: Signal, - { limit, ac }: { limit?: number; ac?: AbortController }, -) { - ;(async () => { - const newsList = requestApiStream( - 'news/fetchAll', - 'GET', - ) - - for await (const news of newsList) { - list.value = [ - ...list.value, - BlogCard({ ...news, lastUpdate: new Date(news.lastUpdate) }), - ] - if (limit && list.value.length >= limit) break - } - ac?.abort() - })() -} +import CardList from ':islands/CardList.tsx' +import type { Ref } from 'preact' export default function BlogCardList( { limit, usePlaceholder, useObserver }: { - usePlaceholder?: boolean limit?: number + usePlaceholder?: boolean useObserver?: boolean }, ) { - const list: Signal = useSignal([]) - const ac = new AbortController() - const ref = useRef(null) - - useEffect(() => { - if (ref.current && useObserver) { - const observer = new IntersectionObserver(([entry]) => { - if (entry.isIntersecting) { - fillList(list, { limit, ac }) - observer.disconnect() - } - }, { - rootMargin: '300px', - }) - //@ts-expect-error Need to investigate why it's work - observer.observe(ref.current.base) - } else { - fillList(list, { limit, ac }) - } - }) - - if (limit && usePlaceholder) { - const placeholders = Array - .from({ length: limit }) - .map((_, index) => ( - } - fallback={Fallback} - signal={ac.signal} - > - {updateFromList(list, index)} - - )) - return <>{placeholders} + if (usePlaceholder) { + return ( + + ) } - return <>{list} + return ( + + ) +} + +function builder(news: BlogProps) { + return BlogCard({ ...news, lastUpdate: new Date(news.lastUpdate) }) } function Placeholder({ ref }: { ref?: Ref | undefined }) { @@ -93,18 +56,3 @@ function Fallback() { ) } - -function updateFromList( - list: Signal, - index: number, -): Promise { - const { promise, resolve } = Promise.withResolvers() - list.subscribe((value: JSX.Element[]) => { - const selected = value.at(index) - if (selected) { - resolve(selected) - } - }) - - return promise -} diff --git a/islands/CardList.tsx b/islands/CardList.tsx new file mode 100644 index 0000000..32bda73 --- /dev/null +++ b/islands/CardList.tsx @@ -0,0 +1,102 @@ +import Suspense, { Fallback } from ':islands/Suspens.tsx' +import { requestApiStream } from ':src/utils.ts' +import { Signal, useSignal } from '@preact/signals' +import type { JSX, Ref } from 'preact' +import { useEffect, useRef } from 'preact/hooks' + +export type Builder = (props: T) => JSX.Element + +export type CardListProps = { + apiRoute: string + builder: Builder + limit?: number +} | { + apiRoute: string + builder: Builder + limit?: number + useObserver?: boolean + placeholder: ({ ref }: { ref: Ref | undefined }) => JSX.Element + fallback: Fallback +} + +export default function CardList( + { limit, builder, ...props }: CardListProps, +) { + const list: Signal = useSignal([]) + const ac = new AbortController() + const ref = useRef(null) + + const useObserver = 'useObserver' in props ? props.useObserver : false + const placeholder = 'placeholder' in props ? props.placeholder : false + const fallback = 'fallback' in props ? props.fallback : false + + useEffect(() => { + if (ref.current && useObserver) { + const observer = new IntersectionObserver(([entry]) => { + if (entry.isIntersecting) { + fillList(list, builder, { limit, ac }) + observer.disconnect() + } + }, { + rootMargin: '300px', + }) + observer.observe(ref.current) + } else { + fillList(list, builder, { limit, ac }) + } + }) + + if (limit && placeholder && fallback) { + const placeholders = Array + .from({ length: limit }) + .map((_, index) => ( + + {updateFromList(list, index)} + + )) + return <>{placeholders} + } + + return <>{list} +} + +function fillList( + list: Signal, + builder: Builder, + { limit, ac }: { limit?: number; ac?: AbortController }, +) { + ;(async () => { + const propsList = requestApiStream( + 'news/fetchAll', + 'GET', + ) + + for await (const props of propsList) { + list.value = [ + ...list.value, + builder(props), + ] + if (limit && list.value.length >= limit) break + } + ac?.abort() + })() +} + +function updateFromList( + list: Signal, + index: number, +): Promise { + const { promise, resolve } = Promise.withResolvers() + list.subscribe((value: JSX.Element[]) => { + const selected = value.at(index) + if (selected) { + resolve(selected) + } + }) + + return promise +} diff --git a/islands/Suspens.tsx b/islands/Suspens.tsx index 779f8f3..0054df4 100644 --- a/islands/Suspens.tsx +++ b/islands/Suspens.tsx @@ -16,7 +16,7 @@ function RenderError( ) } -type Fallback = ({ error }: { error: Error }) => JSX.Element +export type Fallback = ({ error }: { error: Error }) => JSX.Element export default function Suspense( { loader, fallback, signal, children }: { diff --git a/routes/index.tsx b/routes/index.tsx index 28e82d6..8fc5e61 100644 --- a/routes/index.tsx +++ b/routes/index.tsx @@ -19,7 +19,7 @@ export default function Home() {

Nos actus

<> - + Voir plus