-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Add preact persist plugin #10120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add preact persist plugin #10120
Changes from all commits
601d6a7
bee7a6a
ab89721
e547e4e
3bb53d3
57b1cde
0271da9
f5a9ab1
181e8ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| id: createAsyncStoragePersister | ||
| title: createAsyncStoragePersister | ||
| ref: docs/framework/react/plugins/createAsyncStoragePersister.md | ||
| replace: { 'react-query': 'preact-query' } | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| id: createPersister | ||
| title: experimental_createPersister | ||
| ref: docs/framework/react/plugins/createPersister.md | ||
| replace: { 'react-query': 'preact-query' } | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| id: createSyncStoragePersister | ||
| title: createSyncStoragePersister | ||
| ref: docs/framework/react/plugins/createSyncStoragePersister.md | ||
| replace: { 'react-query': 'preact-query' } | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // @ts-check | ||
| // @ts-ignore: no types for eslint-config-preact | ||
| import preact from 'eslint-config-preact' | ||
| // eslint-config-preact uses typescript-eslint under the hood | ||
| import tseslint from 'typescript-eslint' | ||
|
|
||
| import rootConfig from './root.eslint.config.js' | ||
|
|
||
| export default [ | ||
| ...rootConfig, | ||
| ...preact, | ||
| { | ||
| files: ['**/*.{ts,tsx}'], | ||
| languageOptions: { | ||
| parser: tseslint.parser, | ||
| parserOptions: { | ||
| project: true, | ||
| }, | ||
| }, | ||
| plugins: { | ||
| 'typescript-eslint': tseslint.plugin, | ||
| }, | ||
| rules: { | ||
| // Disable base rule to prevent overload false positives | ||
| 'no-redeclare': 'off', | ||
| 'no-duplicate-imports': 'off', | ||
| 'no-unused-vars': 'off', | ||
| 'import/order': 'off', | ||
| 'sort-imports': 'off', | ||
| 'no-import-assign': 'off', | ||
| // TS-aware version handles overloads correctly | ||
| '@typescript-eslint/no-redeclare': 'error', | ||
| '@typescript-eslint/array-type': 'off', | ||
| '@typescript-eslint/no-unnecessary-type-assertion': 'off', | ||
| '@typescript-eslint/no-unnecessary-condition': 'off', | ||
| }, | ||
| }, | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| { | ||
| "name": "@tanstack/preact-query-persist-client", | ||
| "version": "5.91.0", | ||
| "description": "Preact bindings to work with persisters in TanStack/preact-query", | ||
| "author": "tannerlinsley", | ||
| "license": "MIT", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/TanStack/query.git", | ||
| "directory": "packages/preact-query-persist-client" | ||
| }, | ||
| "homepage": "https://tanstack.com/query", | ||
| "funding": { | ||
| "type": "github", | ||
| "url": "https://github.com/sponsors/tannerlinsley" | ||
| }, | ||
| "scripts": { | ||
| "clean": "premove ./build ./coverage ./dist-ts", | ||
| "compile": "tsc --build", | ||
| "test:eslint": "eslint --concurrency=auto ./src", | ||
| "test:types": "npm-run-all --serial test:types:*", | ||
| "test:types:ts50": "node ../../node_modules/typescript50/lib/tsc.js --build tsconfig.legacy.json", | ||
| "test:types:ts51": "node ../../node_modules/typescript51/lib/tsc.js --build tsconfig.legacy.json", | ||
| "test:types:ts52": "node ../../node_modules/typescript52/lib/tsc.js --build tsconfig.legacy.json", | ||
| "test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js --build tsconfig.legacy.json", | ||
| "test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js --build tsconfig.legacy.json", | ||
| "test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js --build tsconfig.legacy.json", | ||
| "test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build tsconfig.legacy.json", | ||
| "test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js --build tsconfig.legacy.json", | ||
| "test:types:tscurrent": "tsc --build", | ||
| "test:lib": "vitest --retry=3", | ||
| "test:lib:dev": "pnpm run test:lib --watch", | ||
| "test:build": "publint --strict && attw --pack", | ||
| "build": "tsup --tsconfig tsconfig.prod.json" | ||
| }, | ||
| "type": "module", | ||
| "types": "build/legacy/index.d.ts", | ||
| "main": "build/legacy/index.cjs", | ||
| "module": "build/legacy/index.js", | ||
| "exports": { | ||
| ".": { | ||
| "@tanstack/custom-condition": "./src/index.ts", | ||
| "import": { | ||
| "types": "./build/modern/index.d.ts", | ||
| "default": "./build/modern/index.js" | ||
| }, | ||
| "require": { | ||
| "types": "./build/modern/index.d.cts", | ||
| "default": "./build/modern/index.cjs" | ||
| } | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "sideEffects": false, | ||
| "files": [ | ||
| "build", | ||
| "src", | ||
| "!src/__tests__" | ||
| ], | ||
| "dependencies": { | ||
| "@tanstack/query-persist-client-core": "workspace:*" | ||
| }, | ||
| "devDependencies": { | ||
| "@preact/preset-vite": "^2.10.2", | ||
| "@tanstack/preact-query": "workspace:*", | ||
| "@tanstack/query-test-utils": "workspace:*", | ||
| "@testing-library/preact": "^3.2.4", | ||
| "eslint-config-preact": "^2.0.0", | ||
| "npm-run-all2": "^5.0.0", | ||
| "preact": "^10.28.0", | ||
| "typescript-eslint": "^8.54.0" | ||
| }, | ||
| "peerDependencies": { | ||
| "@tanstack/preact-query": "workspace:^", | ||
| "preact": "^10.0.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../eslint.config.js |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ../../scripts/getTsupConfig.js |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { useEffect, useRef, useState } from 'preact/hooks' | ||
| import type { VNode } from 'preact' | ||
|
|
||
| import { | ||
| persistQueryClientRestore, | ||
| persistQueryClientSubscribe, | ||
| } from '@tanstack/query-persist-client-core' | ||
| import { | ||
| IsRestoringProvider, | ||
| QueryClientProvider, | ||
| } from '@tanstack/preact-query' | ||
| import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core' | ||
| import type { | ||
| OmitKeyof, | ||
| QueryClientProviderProps, | ||
| } from '@tanstack/preact-query' | ||
|
|
||
| export type PersistQueryClientProviderProps = QueryClientProviderProps & { | ||
| persistOptions: OmitKeyof<PersistQueryClientOptions, 'queryClient'> | ||
| onSuccess?: () => Promise<unknown> | unknown | ||
| onError?: () => Promise<unknown> | unknown | ||
| } | ||
|
|
||
| export const PersistQueryClientProvider = ({ | ||
| children, | ||
| persistOptions, | ||
| onSuccess, | ||
| onError, | ||
| ...props | ||
| }: PersistQueryClientProviderProps): VNode => { | ||
| const [isRestoring, setIsRestoring] = useState(true) | ||
| const refs = useRef({ persistOptions, onSuccess, onError }) | ||
| const didRestore = useRef(false) | ||
|
|
||
| useEffect(() => { | ||
| refs.current = { persistOptions, onSuccess, onError } | ||
| }) | ||
|
|
||
| useEffect(() => { | ||
| const options = { | ||
| ...refs.current.persistOptions, | ||
| queryClient: props.client, | ||
| } | ||
| if (!didRestore.current) { | ||
| didRestore.current = true | ||
| persistQueryClientRestore(options) | ||
| .then(() => refs.current.onSuccess?.()) | ||
| .catch(() => refs.current.onError?.()) | ||
| .finally(() => { | ||
| setIsRestoring(false) | ||
| }) | ||
| } | ||
| return isRestoring ? undefined : persistQueryClientSubscribe(options) | ||
| }, [props.client, isRestoring]) | ||
|
Comment on lines
+39
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cd . && find . -type f -name "*PersistQueryClientProvider*" | head -20Repository: TanStack/query Length of output: 617 🏁 Script executed: ls -la packages/Repository: TanStack/query Length of output: 1931 🏁 Script executed: find . -name "PersistQueryClientProvider.tsx" -type fRepository: TanStack/query Length of output: 277 🏁 Script executed: git ls-files | grep "PersistQueryClientProvider"Repository: TanStack/query Length of output: 603 🏁 Script executed: git ls-files "**/*PersistQueryClientProvider*"Repository: TanStack/query Length of output: 603 🏁 Script executed: find packages -type f -name "*.tsx" | grep -i providerRepository: TanStack/query Length of output: 855 🏁 Script executed: find . -type f -name "*.tsx" -o -name "*.ts" | xargs grep -l "PersistQueryClientProvider" 2>/dev/nullRepository: TanStack/query Length of output: 953 🏁 Script executed: tree -L 3 -d 2>/dev/null || find . -maxdepth 3 -type d | head -30Repository: TanStack/query Length of output: 6546 🏁 Script executed: pwd && ls -laRepository: TanStack/query Length of output: 2437 🏁 Script executed: find . -path "*/preact-query-persist-client/*" -type fRepository: TanStack/query Length of output: 623 🏁 Script executed: rg "didRestore.current" -t tsx -t tsRepository: TanStack/query Length of output: 85 🏁 Script executed: git ls-files | wc -l && git ls-files | grep -i providerRepository: TanStack/query Length of output: 2578 🏁 Script executed: git ls-files | grep "\.tsx$"Repository: TanStack/query Length of output: 17194 🏁 Script executed: git log --oneline --all | head -5Repository: TanStack/query Length of output: 340 🏁 Script executed: git ls-files --full-name | sortRepository: TanStack/query Length of output: 50371 🏁 Script executed: git ls-files --full-name | grep -i persistRepository: TanStack/query Length of output: 9283 🏁 Script executed: find . -type f -name "*.tsx" | wc -lRepository: TanStack/query Length of output: 61 🏁 Script executed: rg "useEffect" --type tsx --type ts | grep -i "persistquery\|restore" -A 5Repository: TanStack/query Length of output: 85 🏁 Script executed: rg "persistQueryClientRestore"Repository: TanStack/query Length of output: 2830 🏁 Script executed: git ls-files | head -100Repository: TanStack/query Length of output: 4804 🏁 Script executed: cat /dev/null && pwdRepository: TanStack/query Length of output: 76 🏁 Script executed: git remote -v 2>/dev/null || echo "No git remote found"Repository: TanStack/query Length of output: 174 🏁 Script executed: git rev-parse --show-toplevel 2>/dev/null || echo "Not a git repo"Repository: TanStack/query Length of output: 76 🏁 Script executed: test -f packages/preact-query-persist-client/src/PersistQueryClientProvider.tsx && echo "File exists" || echo "File not found"Repository: TanStack/query Length of output: 69 🏁 Script executed: find . -iname "*react*query*persist*" -o -iname "*preact*query*persist*" 2>/dev/nullRepository: TanStack/query Length of output: 134 🌐 Web query:
💡 Result: In TanStack Query (v5), restoration/persistence works like this:
What happens on unmount/remount?
Manual pattern (if you’re not using
|
||
|
|
||
| return ( | ||
| <QueryClientProvider {...props}> | ||
| <IsRestoringProvider value={isRestoring}>{children}</IsRestoringProvider> | ||
| </QueryClientProvider> | ||
| ) | ||
| } | ||
|
Comment on lines
+24
to
+61
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| /** @jsxRuntime automatic */ | ||
| /** @jsxImportSource preact */ | ||
| import '@testing-library/jest-dom/vitest' | ||
|
|
||
| import type { | ||
| PersistedClient, | ||
| Persister, | ||
| } from '../../../query-persist-client-core/src' | ||
| import { persistQueryClientSave } from '../../../query-persist-client-core/src' | ||
| import { notifyManager } from '../../../query-core/src' | ||
| import { act, cleanup, render } from '@testing-library/preact' | ||
| import type { UseQueryResult } from '../../../preact-query/src' | ||
| import { QueryClient, useQuery } from '../../../preact-query/src' | ||
| import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' | ||
| import { queryKey, sleep } from '@tanstack/query-test-utils' | ||
|
|
||
| import { PersistQueryClientProvider } from './testPersistProvider' | ||
JoviDeCroock marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| notifyManager.setNotifyFunction((fn) => { | ||
| act(fn) | ||
| }) | ||
|
|
||
| const createMockPersister = (): Persister => { | ||
| let storedState: PersistedClient | undefined | ||
|
|
||
| return { | ||
| persistClient(persistClient: PersistedClient) { | ||
| storedState = persistClient | ||
| return Promise.resolve() | ||
| }, | ||
| async restoreClient() { | ||
| return sleep(10).then(() => storedState) | ||
| }, | ||
| removeClient() { | ||
| storedState = undefined | ||
| return Promise.resolve() | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| describe('PersistQueryClientProvider (preact)', () => { | ||
| beforeEach(() => { | ||
| vi.useFakeTimers() | ||
| }) | ||
|
|
||
| afterEach(() => { | ||
| cleanup() | ||
| vi.useRealTimers() | ||
| }) | ||
|
|
||
| test('restores cache from persister and refetches', async () => { | ||
| const key = queryKey() | ||
| const states: Array<UseQueryResult<string>> = [] | ||
|
|
||
| const queryClient = new QueryClient() | ||
| queryClient.prefetchQuery({ | ||
| queryKey: key, | ||
| queryFn: () => sleep(10).then(() => 'hydrated'), | ||
| }) | ||
| await vi.advanceTimersByTimeAsync(10) | ||
|
|
||
| const persister = createMockPersister() | ||
|
|
||
| persistQueryClientSave({ queryClient, persister }) | ||
| await vi.advanceTimersByTimeAsync(0) | ||
|
|
||
| queryClient.clear() | ||
|
|
||
| function Page() { | ||
| const state = useQuery({ | ||
| queryKey: key, | ||
| queryFn: () => sleep(10).then(() => 'fetched'), | ||
| }) | ||
|
|
||
| states.push(state) | ||
|
|
||
| return ( | ||
| <div> | ||
| <h1>{state.data}</h1> | ||
| <h2>fetchStatus: {state.fetchStatus}</h2> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| const rendered = render( | ||
| <PersistQueryClientProvider | ||
| client={queryClient} | ||
| persistOptions={{ persister }} | ||
| > | ||
| <Page /> | ||
| </PersistQueryClientProvider>, | ||
| ) | ||
|
|
||
| expect(rendered.getByText('fetchStatus: idle')).toBeInTheDocument() | ||
|
|
||
| await act(async () => { | ||
| await vi.advanceTimersByTimeAsync(10) | ||
| }) | ||
| expect(rendered.getByText('hydrated')).toBeInTheDocument() | ||
|
|
||
| await act(async () => { | ||
| await vi.advanceTimersByTimeAsync(11) | ||
| }) | ||
| expect(rendered.getByText('fetched')).toBeInTheDocument() | ||
|
|
||
| expect(states.length).toBeGreaterThanOrEqual(3) | ||
| expect(states[0]).toMatchObject({ | ||
| status: 'pending', | ||
| fetchStatus: 'idle', | ||
| data: undefined, | ||
| }) | ||
|
|
||
| expect( | ||
| states.some( | ||
| (state) => | ||
| state.fetchStatus === 'fetching' && state.data === 'hydrated', | ||
| ), | ||
| ).toBe(true) | ||
|
|
||
| expect(states.at(-1)).toMatchObject({ | ||
| status: 'success', | ||
| fetchStatus: 'idle', | ||
| data: 'fetched', | ||
| }) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix import order: type import from
preactshould follow@tanstack/*imports.ESLint reports that the
preacttype import on Line 2 should occur after the@tanstack/preact-queryimport.🔧 Proposed fix
import { useEffect, useRef, useState } from 'preact/hooks' -import type { VNode } from 'preact' import { persistQueryClientRestore, persistQueryClientSubscribe, } from '@tanstack/query-persist-client-core' import { IsRestoringProvider, QueryClientProvider } from '@tanstack/preact-query' import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core' import type { OmitKeyof, QueryClientProviderProps } from '@tanstack/preact-query' +import type { VNode } from 'preact'📝 Committable suggestion
🧰 Tools
🪛 ESLint
[error] 2-2:
preacttype import should occur after import of@tanstack/preact-query(import/order)
🤖 Prompt for AI Agents