From 25da77f1197d39d4165ced3536adb19de65b1b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20C=2E=20For=C3=A9s?= Date: Wed, 29 Oct 2025 10:45:10 +0100 Subject: [PATCH 1/2] feat: remove memos --- .nvmrc | 2 +- package-lock.json | 28 +++++++- package.json | 1 + src/react/components/QueryPrefetchTags.tsx | 8 +-- src/react/components/QueryProvider.tsx | 18 ++--- src/react/components/QueryTransition.tsx | 11 +-- src/react/hooks/useQuery.ts | 10 +-- src/react/hooks/useQueryActions.ts | 59 ++++++----------- src/react/hooks/useQueryBasic.ts | 70 +++++--------------- src/react/hooks/useQueryContext.ts | 4 +- src/react/hooks/useQueryInstance.test.tsx | 65 ++++++++++++++++++ src/react/hooks/useQueryInstance.ts | 24 +++---- src/react/hooks/useQueryPrefetch.ts | 4 +- src/react/hooks/useQueryStatus.ts | 11 +-- src/react/hooks/useQueryTransitionContext.ts | 4 +- vite.config.ts | 2 +- 16 files changed, 152 insertions(+), 169 deletions(-) create mode 100644 src/react/hooks/useQueryInstance.test.tsx diff --git a/.nvmrc b/.nvmrc index a45fd52..7273c0f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24 +25 diff --git a/package-lock.json b/package-lock.json index 3855680..2406474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@studiolambda/query", - "version": "1.3.0", + "version": "1.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@studiolambda/query", - "version": "1.3.0", + "version": "1.4.0", "license": "MIT", "devDependencies": { "@types/node": "^24.9.1", @@ -26,6 +26,7 @@ "prettier": "^3.6.2", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-error-boundary": "^6.0.0", "solid-js": "^1.9.9", "typescript": "~5.9.3", "typescript-eslint": "^8.46.2", @@ -325,6 +326,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -4521,6 +4532,19 @@ "react": "^19.2.0" } }, + "node_modules/react-error-boundary": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.0.0.tgz", + "integrity": "sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", diff --git a/package.json b/package.json index b48ab61..c296fbe 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "prettier": "^3.6.2", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-error-boundary": "^6.0.0", "solid-js": "^1.9.9", "typescript": "~5.9.3", "typescript-eslint": "^8.46.2", diff --git a/src/react/components/QueryPrefetchTags.tsx b/src/react/components/QueryPrefetchTags.tsx index c91218f..30bc169 100644 --- a/src/react/components/QueryPrefetchTags.tsx +++ b/src/react/components/QueryPrefetchTags.tsx @@ -1,6 +1,5 @@ import type { QueryInstance } from 'query/react:hooks/useQueryInstance' import type { ReactNode, LinkHTMLAttributes } from 'react' -import { useMemo } from 'react' import { useQueryPrefetch } from 'query/react:hooks/useQueryPrefetch' type Additional = LinkHTMLAttributes & QueryInstance @@ -13,12 +12,7 @@ export interface QueryPrefetchTagsProps extends Additional { export function QueryPrefetchTags({ keys, children, ...options }: QueryPrefetchTagsProps) { useQueryPrefetch(keys, options) - const tags = useMemo( - function () { - return keys.map((key) => ) - }, - [keys, options] - ) + const tags = keys.map((key) => ) return ( <> diff --git a/src/react/components/QueryProvider.tsx b/src/react/components/QueryProvider.tsx index 8a3957d..9ccc934 100644 --- a/src/react/components/QueryProvider.tsx +++ b/src/react/components/QueryProvider.tsx @@ -1,6 +1,6 @@ -import { useEffect, useMemo, type ReactNode } from 'react' +import { useEffect, type ReactNode } from 'react' import { Context, type ContextValue } from 'query/react:context' -import { createQuery, type Query } from 'query:index' +import { createQuery } from 'query:index' export interface QueryProviderProps extends ContextValue { children?: ReactNode @@ -12,12 +12,7 @@ export function QueryProvider({ ignoreTransitionContext, query, }: QueryProviderProps) { - const localQuery = useMemo( - function () { - return query ?? createQuery() - }, - [query] - ) + const localQuery = query ?? createQuery() useEffect( function () { @@ -35,12 +30,7 @@ export function QueryProvider({ [localQuery] ) - const value = useMemo( - function (): ContextValue { - return { query: localQuery, clearOnForget, ignoreTransitionContext } - }, - [localQuery, clearOnForget, ignoreTransitionContext] - ) + const value = { query: localQuery, clearOnForget, ignoreTransitionContext } return {children} } diff --git a/src/react/components/QueryTransition.tsx b/src/react/components/QueryTransition.tsx index aec693e..cf3ddeb 100644 --- a/src/react/components/QueryTransition.tsx +++ b/src/react/components/QueryTransition.tsx @@ -1,5 +1,5 @@ -import { TransitionContext, type QueryTransitionContextValue } from 'query/react:transition' -import { useMemo, type ReactNode, type TransitionStartFunction } from 'react' +import { TransitionContext } from 'query/react:transition' +import { type ReactNode, type TransitionStartFunction } from 'react' export interface QueryTransitionProps { isPending: boolean @@ -8,12 +8,7 @@ export interface QueryTransitionProps { } export function QueryTransition({ children, startTransition, isPending }: QueryTransitionProps) { - const value = useMemo( - function (): QueryTransitionContextValue { - return { startTransition, isPending } - }, - [startTransition, isPending] - ) + const value = { startTransition, isPending } return {children} } diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts index 011c83a..aababe1 100644 --- a/src/react/hooks/useQuery.ts +++ b/src/react/hooks/useQuery.ts @@ -1,4 +1,3 @@ -import { useMemo, useDebugValue } from 'react' import { type ContextValue } from 'query/react:context' import { type Options } from 'query:index' import { type QueryInstance } from './useQueryInstance' @@ -16,16 +15,9 @@ export interface Resource extends AdditionalHooks { export type ResourceOptions = ContextValue & Options & QueryInstance export function useQuery(key: string, options?: ResourceOptions): Resource { - useDebugValue('useQuery') - const basic = useQueryBasic(key, options) const actions = useQueryActions(key, options) const status = useQueryStatus(key, options) - return useMemo( - function (): Resource { - return { ...basic, ...actions, ...status } - }, - [basic, actions, status] - ) + return { ...basic, ...actions, ...status } } diff --git a/src/react/hooks/useQueryActions.ts b/src/react/hooks/useQueryActions.ts index fdc46c0..f6ba512 100644 --- a/src/react/hooks/useQueryActions.ts +++ b/src/react/hooks/useQueryActions.ts @@ -1,11 +1,10 @@ import { type MutateOptions, type MutationValue, type Options } from 'query:index' -import { useCallback, useDebugValue, useMemo } from 'react' import { useQueryInstance, type QueryInstance } from './useQueryInstance' export interface QueryActions { readonly refetch: (refetchOptions?: Options) => Promise readonly mutate: (value: MutationValue, options?: MutateOptions) => Promise - readonly forget: () => void + readonly forget: () => Promise } export type QueryActionsOptions = Options & QueryInstance @@ -14,8 +13,6 @@ export function useQueryActions( key: string, options?: QueryActionsOptions ): QueryActions { - useDebugValue('useQueryActions') - const { expiration: oExpiration, fetcher: oFetcher, @@ -26,38 +23,24 @@ export function useQueryActions( const { query, mutate, forget } = useQueryInstance(options) - const refetch = useCallback( - function (refetchOptions?: Options) { - return query(key, { - stale: oStale ?? false, - expiration: oExpiration, - fetcher: oFetcher, - removeOnError: oRemoveOnError, - fresh: oFresh, - ...refetchOptions, - }) - }, - [query, key, oExpiration, oFetcher, oStale, oRemoveOnError, oFresh] - ) - - const localMutate = useCallback( - function (value: MutationValue, options?: MutateOptions) { - return mutate(key, value, options) - }, - [mutate, key] - ) - - const localForget = useCallback( - async function () { - await forget(key) - }, - [forget, key] - ) - - return useMemo( - function () { - return { refetch, mutate: localMutate, forget: localForget } - }, - [refetch, localMutate, localForget] - ) + function refetch(refetchOptions?: Options) { + return query(key, { + stale: oStale ?? false, + expiration: oExpiration, + fetcher: oFetcher, + removeOnError: oRemoveOnError, + fresh: oFresh, + ...refetchOptions, + }) + } + + function localMutate(value: MutationValue, options?: MutateOptions) { + return mutate(key, value, options) + } + + async function localForget() { + await forget(key) + } + + return { refetch, mutate: localMutate, forget: localForget } } diff --git a/src/react/hooks/useQueryBasic.ts b/src/react/hooks/useQueryBasic.ts index 2f74593..12b2946 100644 --- a/src/react/hooks/useQueryBasic.ts +++ b/src/react/hooks/useQueryBasic.ts @@ -1,4 +1,4 @@ -import { useEffect, use, useState, useMemo, useDebugValue, useTransition } from 'react' +import { useEffect, use, useState, useTransition } from 'react' import { type ContextValue } from 'query/react:context' import { type Options } from 'query:index' import { useQueryContext } from './useQueryContext' @@ -16,8 +16,6 @@ export function useQueryBasic( key: string, options?: BasicResourceOptions ): BasicResource { - useDebugValue('useQueryBasic') - const { clearOnForget: cClearOnForget, ignoreTransitionContext: cIgnoreTransitionContext } = useQueryContext() @@ -36,55 +34,22 @@ export function useQueryBasic( const [isLocalPending, startLocalTransition] = useTransition() const { query, expiration, subscribe } = useQueryInstance(options) + const ignoreTransitionContext = oIgnoreTransitionContext ?? cIgnoreTransitionContext ?? false + const isPending = ignoreTransitionContext ? isLocalPending : (isContextPending ?? isLocalPending) - const ignoreTransitionContext = useMemo( - function () { - return oIgnoreTransitionContext ?? cIgnoreTransitionContext ?? false - }, - [oIgnoreTransitionContext, cIgnoreTransitionContext] - ) - - const isPending = useMemo( - function () { - if (ignoreTransitionContext) { - return isLocalPending - } + const startTransition = ignoreTransitionContext + ? startLocalTransition + : (startContextTransition ?? startLocalTransition) - return isContextPending ?? isLocalPending - }, - [isContextPending, isLocalPending, ignoreTransitionContext] - ) - - const startTransition = useMemo( - function () { - if (ignoreTransitionContext) { - return startLocalTransition - } - - return startContextTransition ?? startLocalTransition - }, - [startContextTransition, startLocalTransition, ignoreTransitionContext] - ) + const clearOnForget = oClearOnForget ?? cClearOnForget ?? false - const clearOnForget = useMemo( - function () { - return oClearOnForget ?? cClearOnForget ?? false - }, - [oClearOnForget, cClearOnForget] - ) - - const promise = useMemo( - function () { - return query(key, { - expiration: oExpiration, - fetcher: oFetcher, - stale: oStale, - removeOnError: oRemoveOnError, - fresh: oFresh, - }) - }, - [query, key, oExpiration, oFetcher, oStale, oRemoveOnError, oFresh] - ) + const promise = query(key, { + expiration: oExpiration, + fetcher: oFetcher, + stale: oStale, + removeOnError: oRemoveOnError, + fresh: oFresh, + }) const [data, setData] = useState(use(promise)) @@ -177,10 +142,5 @@ export function useQueryBasic( ] ) - return useMemo( - function (): BasicResource { - return { data, isPending } - }, - [data, isPending] - ) + return { data, isPending } } diff --git a/src/react/hooks/useQueryContext.ts b/src/react/hooks/useQueryContext.ts index 990a419..aefecd9 100644 --- a/src/react/hooks/useQueryContext.ts +++ b/src/react/hooks/useQueryContext.ts @@ -1,8 +1,6 @@ import { Context, type ContextValue } from 'query/react:context' -import { use, useDebugValue } from 'react' +import { use } from 'react' export function useQueryContext(): ContextValue { - useDebugValue('useQueryContext') - return use(Context) } diff --git a/src/react/hooks/useQueryInstance.test.tsx b/src/react/hooks/useQueryInstance.test.tsx new file mode 100644 index 0000000..f905b25 --- /dev/null +++ b/src/react/hooks/useQueryInstance.test.tsx @@ -0,0 +1,65 @@ +import { describe, it } from 'vitest' +import { createQuery } from 'query:index' +import { act, Suspense } from 'react' +import { createRoot } from 'react-dom/client' +import { ErrNoQueryInstanceFound, useQueryInstance } from './useQueryInstance' +import { ErrorBoundary } from 'react-error-boundary' + +describe.concurrent('useQueryInstance', function () { + it('can get a query instance', async ({ expect }) => { + function fetcher(key: string) { + return Promise.resolve(key) + } + + const query = createQuery({ fetcher }) + const options = { query } + + function Component() { + const query = useQueryInstance(options) + expect(query).not.toBeNull() + + return null + } + + const container = document.createElement('div') + + // eslint-disable-next-line + await act(async function () { + createRoot(container).render( + + + + ) + }) + }) + + it('can throws if no query instance is found', async ({ expect }) => { + function Component() { + const query = useQueryInstance() + expect(query).toBeNull() + + return null + } + + const container = document.createElement('div') + let err: Error | undefined = undefined + + // eslint-disable-next-line + await act(async function () { + function onError(e: Error) { + err = e + } + + createRoot(container).render( + } onError={onError}> + + + + + ) + }) + + expect(err).toBeDefined() + expect(err).toEqual(new Error(ErrNoQueryInstanceFound)) + }) +}) diff --git a/src/react/hooks/useQueryInstance.ts b/src/react/hooks/useQueryInstance.ts index 65907d4..4137d95 100644 --- a/src/react/hooks/useQueryInstance.ts +++ b/src/react/hooks/useQueryInstance.ts @@ -1,29 +1,21 @@ import { type Query } from 'query:index' import { useQueryContext } from 'query/react:hooks/useQueryContext' -import { useDebugValue, useMemo } from 'react' export interface QueryInstance { readonly query?: Query } -export function useQueryInstance(options?: QueryInstance): Query { - useDebugValue('useQueryInstance') +export const ErrNoQueryInstanceFound = + 'No query instance was found. Please provide one via the resource options or the query context.' +export function useQueryInstance(options?: QueryInstance): Query { const { query: cQuery } = useQueryContext() const { query: oQuery } = options ?? {} + const instance = oQuery ?? cQuery - return useMemo( - function () { - const instance = oQuery ?? cQuery - - if (!instance) { - throw new Error( - 'No query instance was found. Please provide one via the resource options or the query context.' - ) - } + if (!instance) { + throw new Error(ErrNoQueryInstanceFound) + } - return instance - }, - [oQuery, cQuery] - ) + return instance } diff --git a/src/react/hooks/useQueryPrefetch.ts b/src/react/hooks/useQueryPrefetch.ts index ce58976..6703a5a 100644 --- a/src/react/hooks/useQueryPrefetch.ts +++ b/src/react/hooks/useQueryPrefetch.ts @@ -1,9 +1,7 @@ -import { useDebugValue, useEffect } from 'react' +import { useEffect } from 'react' import { QueryInstance, useQueryInstance } from './useQueryInstance' export function useQueryPrefetch(keys: string[], options?: QueryInstance) { - useDebugValue('useQueryPrefetch') - const { query } = useQueryInstance(options) useEffect( diff --git a/src/react/hooks/useQueryStatus.ts b/src/react/hooks/useQueryStatus.ts index 6ab1150..7cf4226 100644 --- a/src/react/hooks/useQueryStatus.ts +++ b/src/react/hooks/useQueryStatus.ts @@ -1,4 +1,4 @@ -import { useDebugValue, useEffect, useMemo, useState } from 'react' +import { useEffect, useState } from 'react' import { useQueryInstance, type QueryInstance } from './useQueryInstance' export interface Status { @@ -9,8 +9,6 @@ export interface Status { } export function useQueryStatus(key: string, options?: QueryInstance): Status { - useDebugValue('useQueryStatus') - const { expiration, subscribe } = useQueryInstance(options) const [expiresAt, setExpiresAt] = useState(() => expiration(key) ?? new Date()) const [isExpired, setIsExpired] = useState(() => Date.now() > expiresAt.getTime()) @@ -86,10 +84,5 @@ export function useQueryStatus(key: string, options?: QueryInstance): Status { [key, subscribe, expiration] ) - return useMemo( - function () { - return { expiresAt, isExpired, isRefetching, isMutating } - }, - [expiresAt, isExpired, isRefetching, isMutating] - ) + return { expiresAt, isExpired, isRefetching, isMutating } } diff --git a/src/react/hooks/useQueryTransitionContext.ts b/src/react/hooks/useQueryTransitionContext.ts index c9a57e6..02c21cf 100644 --- a/src/react/hooks/useQueryTransitionContext.ts +++ b/src/react/hooks/useQueryTransitionContext.ts @@ -1,8 +1,6 @@ import { TransitionContext, type QueryTransitionContextValue } from 'query/react:transition' -import { use, useDebugValue } from 'react' +import { use } from 'react' export function useQueryTransitionContext(): QueryTransitionContextValue { - useDebugValue('useQueryTransitionContext') - return use(TransitionContext) } diff --git a/vite.config.ts b/vite.config.ts index bcee3e1..0bd6fae 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -42,7 +42,7 @@ export default defineConfig({ react({ include: ['src/react/**/*.tsx'], babel: { - plugins: ['babel-plugin-react-compiler'], + plugins: [['babel-plugin-react-compiler']], }, }), // solid({ From 82a46511ddb956d14b540ab2d148db79db7e64c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erik=20C=2E=20For=C3=A9s?= Date: Wed, 29 Oct 2025 10:49:58 +0100 Subject: [PATCH 2/2] fix: upgrade node version --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4744a4..cbf81f8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 24.0.0 + node-version: 25.0.0 cache: 'npm' cache-dependency-path: package-lock.json @@ -39,7 +39,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 24.0.0 + node-version: 25.0.0 cache: 'npm' cache-dependency-path: package-lock.json @@ -59,7 +59,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 24.0.0 + node-version: 25.0.0 cache: 'npm' cache-dependency-path: package-lock.json @@ -81,7 +81,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 24.0.0 + node-version: 25.0.0 cache: 'npm' cache-dependency-path: package-lock.json