From 6cf9dc7547d9358b1e202a71f77f940db038376f Mon Sep 17 00:00:00 2001 From: HK-SHAO Date: Thu, 6 Feb 2025 21:50:47 +0800 Subject: [PATCH] Update README, version, and enhance components with new features and improved styling --- README.md | 2 +- package.json | 4 +- src/libs/components/DemoPage.mdx | 35 ++++++++++-- src/libs/components/LatestVersion.tsx | 10 ++-- src/libs/components/Title.tsx | 2 +- src/libs/components/UseAsyncDemo.tsx | 19 ++----- src/libs/components/UseAsyncMemoDemo.tsx | 69 ++++++++++++++++++++++++ src/libs/constants/inspectorTheme.ts | 13 +++++ src/libs/constants/packageJsonUrl.ts | 3 ++ src/libs/constants/version.ts | 2 +- src/libs/hooks/useAsyncMemo.ts | 4 +- src/libs/utils/isAsyncFunction.ts | 2 +- src/libs/utils/isReactMemo.ts | 2 +- src/libs/utils/sameProps.ts | 2 +- 14 files changed, 137 insertions(+), 32 deletions(-) create mode 100644 src/libs/components/UseAsyncMemoDemo.tsx create mode 100644 src/libs/constants/inspectorTheme.ts create mode 100644 src/libs/constants/packageJsonUrl.ts diff --git a/README.md b/README.md index 354916c..0d961e9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ npm i react-client-async You can use the `useAsync` hook to create a task. ```tsx -console.log(useAsync(fn, args, options)); +console.log(useAsync(promiseFn, args, options)); ``` diff --git a/package.json b/package.json index 63a6f56..ad50a01 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-client-async", - "version": "1.3.3", + "version": "1.3.4", "main": "dist/lib.js", "module": "dist/lib.js", "files": ["dist/", "README.md", "LICENSE"], @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/HK-SHAO/react-client-async.git" }, - "description": "React tools for async rendering in client side.", + "description": "React tools for async rendering in client side! 🚀", "readme": "README.md", "keywords": [ "react", diff --git a/src/libs/components/DemoPage.mdx b/src/libs/components/DemoPage.mdx index ffadc88..56fab94 100644 --- a/src/libs/components/DemoPage.mdx +++ b/src/libs/components/DemoPage.mdx @@ -1,11 +1,14 @@ import RecursiveAsyncDemo from '#components/RecursiveAsyncDemo'; +import UseAsyncMemoDemo from '#components/UseAsyncMemoDemo'; +import LatestVersion from '#components/LatestVersion'; import UseAsyncDemo from '#components/UseAsyncDemo'; import AsyncDemo from '#components/AsyncDemo'; -import LatestVersion from './LatestVersion'; import Footer from "#components/Footer"; import Title from "#components/Title"; import Card from "#components/Card"; +import { description } from '#src/../package.json'; + import { toast } from 'react-toastify';
@@ -15,7 +18,7 @@ import { toast } from 'react-toastify'; # <div className="w-full text-center -mt-8 mb-10 p-0"> - <p className='m-0 p-0 text-base/snug text-gray-500 font-bold'>Easy to use async function in React components! 🚀</p> + <p className='m-0 p-0 text-base/snug text-gray-500 font-bold'>{description}</p> </div> <div className='w-full flex justify-center -mt-4'> @@ -57,7 +60,7 @@ npm i react-client-async You can use the `useAsync` hook to create a task. ```tsx -console.log(useAsync(fn, args, options)); +console.log(useAsync(promiseFn, args, options)); ``` <UseAsyncDemo /> @@ -85,7 +88,7 @@ You can use the `Async` to render an async component. <Card className='mt-10 pb-8'> -## <a href="#RecursiveDemo" id="RecursiveDemo">🎬 `Demo` of Recursive Async Component</a> +## <a href="#RecursiveDemo" id="RecursiveDemo">🎬 Recursive Async Component Demo</a> Easy to `wrap` a recursive async component and memoize it. @@ -108,6 +111,30 @@ const Rec: FC<{ n: number }> = wrap( </Card> +<Card className='mt-10 pb-8'> + +## <a href='#useAsyncMemo' id="useAsyncMemo">✅ `useAsyncMemo` Hook</a> + +Use the `useAsyncMemo` hook to create a memoized async task. + +```tsx +const { + state: { result, pending, error }, + load, stop +} = useAsyncMemo( + async ({ signal }) => + fetch("/package.json", { signal }) + .then((res) => res.json()), + [/* Function Dependencies */], + { autoLoad: false }, +); +``` + +<UseAsyncMemoDemo/> + +</Card> + + <Card className='mt-10 pb-2 mb-10'> ## <a href='#Next' id="Next">⏳ What is Next?</a> diff --git a/src/libs/components/LatestVersion.tsx b/src/libs/components/LatestVersion.tsx index 70bce07..aef8782 100644 --- a/src/libs/components/LatestVersion.tsx +++ b/src/libs/components/LatestVersion.tsx @@ -1,7 +1,10 @@ import { useCallback } from 'react'; import { useAsyncMemo } from '#src/lib'; -import delayWithSignal from '../utils/delayWithSignal'; +import packageJsonUrl from '#constants/packageJsonUrl'; +import delayWithSignal from '#utils/delayWithSignal'; + +type PackageJson = typeof import('#src/../package.json'); export default function LatestVersion() { const { @@ -11,8 +14,9 @@ export default function LatestVersion() { } = useAsyncMemo( async ({ signal }) => { const [ret] = await Promise.all([ - // ToDo: Dynamic import with signal - import('#constants/version').then((m) => m.default), + fetch(packageJsonUrl, { signal }) + .then((res) => res.json()) + .then((data: PackageJson) => data.version), // Avoid too fast loading delayWithSignal(400, signal), ]); diff --git a/src/libs/components/Title.tsx b/src/libs/components/Title.tsx index e311e9e..b4491c1 100644 --- a/src/libs/components/Title.tsx +++ b/src/libs/components/Title.tsx @@ -3,7 +3,7 @@ const title = 'React Async for Client'; export default function Title() { return ( <> - <div className="relative w-full h-auto font-bold scale-100 hover:scale-130 transition-transform"> + <div className="relative w-full h-auto font-bold scale-100 hover:scale-110 transition-transform"> <div className="inset-0 flex justify-center items-center bg-clip-text blur-2xl py-4 w-full text-7xl text-center text-transparent pointer-events-none select-none gradient-bg"> {title} </div> diff --git a/src/libs/components/UseAsyncDemo.tsx b/src/libs/components/UseAsyncDemo.tsx index f1cb28e..f1d552d 100644 --- a/src/libs/components/UseAsyncDemo.tsx +++ b/src/libs/components/UseAsyncDemo.tsx @@ -1,20 +1,11 @@ import { type RefObject, useCallback, useRef } from 'react'; import { useAsync } from 'react-client-async'; -import { ObjectInspector, chromeDark } from 'react-inspector'; +import { ObjectInspector } from 'react-inspector'; import { toast } from 'react-toastify'; +import inspectorTheme from '#constants/inspectorTheme'; import delayWithSignal from '#utils/delayWithSignal'; -const inspectorTheme: typeof chromeDark = { - ...chromeDark, - ...{ - BASE_BACKGROUND_COLOR: 'transparent', - BASE_FONT_SIZE: 'var(--text-sm)', - ARROW_FONT_SIZE: 'var(--text-sm)' as unknown as number, - TREENODE_FONT_SIZE: 'var(--text-sm)', - }, -}; - const abortedByStop = Symbol('Aborted By Stop'); const fetchSome = async ( @@ -46,11 +37,7 @@ export default function UseAsyncDemo() { className="flex flex-col justify-center items-center gap-2 py-4 p-2 rounded-md prose-pre" style={{ textShadow: 'rgba(0, 0, 0, 0.3) 0px 1px' }} > - <ObjectInspector - data={task} - expandLevel={3} - theme={inspectorTheme as unknown as 'chromeDark'} - /> + <ObjectInspector data={task} expandLevel={3} theme={inspectorTheme} /> <div className="bg-gray-500/10 m-2 py-[0.5px] w-full" /> <div className="flex gap-4 w-[24em]"> diff --git a/src/libs/components/UseAsyncMemoDemo.tsx b/src/libs/components/UseAsyncMemoDemo.tsx new file mode 100644 index 0000000..0c8bcc2 --- /dev/null +++ b/src/libs/components/UseAsyncMemoDemo.tsx @@ -0,0 +1,69 @@ +import { ObjectInspector } from 'react-inspector'; +import inspectorTheme from '#constants/inspectorTheme'; +import packageJsonUrl from '#constants/packageJsonUrl'; +import { useAsyncMemo } from '#src/lib'; +import delayWithSignal from '../utils/delayWithSignal'; + +type PackageJson = typeof import('#src/../package.json'); + +const whiteList = ['name', 'version', 'author', 'license', 'homepage']; +const whiteListSet = new Set(whiteList); + +export default function UseAsyncMemoDemo() { + const { + state: { result, pending, error }, + load, + stop, + } = useAsyncMemo( + async ({ signal }) => + delayWithSignal(200, signal) + .then(() => fetch(packageJsonUrl, { signal })) + .then((res) => res.json() as Promise<PackageJson>), + [ + /* No dependencies */ + ], + { autoLoad: false }, + ); + + const newResult = + result && typeof result === 'object' + ? Object.fromEntries( + Object.entries(result).filter(([key]) => whiteListSet.has(key)), + ) + : result; + + const data = { result: newResult, pending, error }; + + return ( + <> + <div + className="flex flex-col justify-center items-center gap-2 py-4 p-2 rounded-md prose-pre" + style={{ textShadow: 'rgba(0, 0, 0, 0.3) 0px 1px' }} + > + <div className="w-full"> + <ObjectInspector data={data} expandLevel={3} theme={inspectorTheme} /> + </div> + + <div className="bg-gray-500/10 m-2 py-[0.5px] w-full" /> + <div className="flex gap-4 w-[24em]"> + <button + type="button" + className="flex-1 text-base btn btn-blue" + onClick={load} + disabled={pending} + > + {pending ? 'Loading...' : 'Load'} + </button> + <button + type="button" + className="flex-1 text-base btn btn-red" + onClick={stop} + disabled={!pending} + > + Stop + </button> + </div> + </div> + </> + ); +} diff --git a/src/libs/constants/inspectorTheme.ts b/src/libs/constants/inspectorTheme.ts new file mode 100644 index 0000000..3f7a903 --- /dev/null +++ b/src/libs/constants/inspectorTheme.ts @@ -0,0 +1,13 @@ +import { chromeDark } from 'react-inspector'; + +const inspectorTheme: typeof chromeDark = { + ...chromeDark, + ...{ + BASE_BACKGROUND_COLOR: 'transparent', + BASE_FONT_SIZE: 'var(--text-sm)', + ARROW_FONT_SIZE: 'var(--text-sm)' as unknown as number, + TREENODE_FONT_SIZE: 'var(--text-sm)', + }, +}; + +export default inspectorTheme as unknown as 'chromeDark'; diff --git a/src/libs/constants/packageJsonUrl.ts b/src/libs/constants/packageJsonUrl.ts new file mode 100644 index 0000000..6bb457d --- /dev/null +++ b/src/libs/constants/packageJsonUrl.ts @@ -0,0 +1,3 @@ +const packageJsonUrl = new URL('../../../package.json', import.meta.url).href; + +export default packageJsonUrl; diff --git a/src/libs/constants/version.ts b/src/libs/constants/version.ts index b570dad..15014ee 100644 --- a/src/libs/constants/version.ts +++ b/src/libs/constants/version.ts @@ -1,3 +1,3 @@ -import { version } from '../../../package.json'; +import { version } from '#src/../package.json'; export default version; diff --git a/src/libs/hooks/useAsyncMemo.ts b/src/libs/hooks/useAsyncMemo.ts index c0b9ca1..1fb85c0 100644 --- a/src/libs/hooks/useAsyncMemo.ts +++ b/src/libs/hooks/useAsyncMemo.ts @@ -3,16 +3,18 @@ import { type DependencyList, useMemo } from 'react'; import useAsync, { type UseAsyncFn, type UseAsyncFnExtras, + type UseAsyncOptions, } from '#hooks/useAsync'; export default function useAsyncMemo<T>( promiseFn: (extras: UseAsyncFnExtras) => Promise<T>, deps: DependencyList, + options: UseAsyncOptions<null> = {}, ) { // biome-ignore lint/correctness/useExhaustiveDependencies: only deps are needed const fn = useMemo<UseAsyncFn<null, T>>( () => (_, extras) => promiseFn(extras), deps, ); - return useAsync(fn, null); + return useAsync(fn, null, options); } diff --git a/src/libs/utils/isAsyncFunction.ts b/src/libs/utils/isAsyncFunction.ts index 0d8895d..8b7f67d 100644 --- a/src/libs/utils/isAsyncFunction.ts +++ b/src/libs/utils/isAsyncFunction.ts @@ -1,4 +1,4 @@ -import { AsyncFunction } from '../constants/basic'; +import { AsyncFunction } from '#constants/basic'; /** * Check if the function is an async function. diff --git a/src/libs/utils/isReactMemo.ts b/src/libs/utils/isReactMemo.ts index bdfcdd9..3ce0cdc 100644 --- a/src/libs/utils/isReactMemo.ts +++ b/src/libs/utils/isReactMemo.ts @@ -1,4 +1,4 @@ -import type { MemoComponent } from '../types/react'; +import type { MemoComponent } from '#types/react'; /** * Check if the value is a React memo component. diff --git a/src/libs/utils/sameProps.ts b/src/libs/utils/sameProps.ts index 5283a52..0c7b32b 100644 --- a/src/libs/utils/sameProps.ts +++ b/src/libs/utils/sameProps.ts @@ -1,4 +1,4 @@ -import type { propsAreEqual } from '../types/react'; +import type { propsAreEqual } from '#types/react'; /** * Check if the props are the same.