snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
27
examples/react/algolia/.gitignore
vendored
Normal file
27
examples/react/algolia/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
6
examples/react/algolia/README.md
Normal file
6
examples/react/algolia/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Example
|
||||
|
||||
To run this example:
|
||||
|
||||
- `npm install`
|
||||
- `npm run dev`
|
||||
9
examples/react/algolia/eslint.config.js
Normal file
9
examples/react/algolia/eslint.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { tanstackConfig } from '@tanstack/eslint-config'
|
||||
import pluginQuery from '@tanstack/eslint-plugin-query'
|
||||
import pluginReact from '@eslint-react/eslint-plugin'
|
||||
|
||||
export default [
|
||||
...tanstackConfig,
|
||||
...pluginQuery.configs['flat/recommended'],
|
||||
pluginReact.configs.recommended,
|
||||
]
|
||||
16
examples/react/algolia/index.html
Normal file
16
examples/react/algolia/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" type="image/svg+xml" href="/emblem-light.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<title>TanStack Query React Algolia example App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
25
examples/react/algolia/package.json
Normal file
25
examples/react/algolia/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@tanstack/query-example-react-algolia",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@algolia/client-search": "5.2.1",
|
||||
"@tanstack/react-query": "^5.102.8",
|
||||
"@tanstack/react-query-devtools": "^5.102.8",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/eslint-plugin-query": "^5.102.8",
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"typescript": "5.8.3",
|
||||
"vite": "^6.4.1"
|
||||
}
|
||||
}
|
||||
17
examples/react/algolia/src/App.tsx
Normal file
17
examples/react/algolia/src/App.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
import './styles.css'
|
||||
import Search from './Search'
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<div className="App">
|
||||
<h1>TanStack Query with Algolia</h1>
|
||||
<Search />
|
||||
</div>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
24
examples/react/algolia/src/Search.tsx
Normal file
24
examples/react/algolia/src/Search.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import SearchResults from './SearchResults'
|
||||
|
||||
export default function Search() {
|
||||
const [query, setQuery] = React.useState('')
|
||||
|
||||
const handleOnChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
event.preventDefault()
|
||||
// It is recommended to debounce this event in prod
|
||||
setQuery(event.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
onChange={handleOnChange}
|
||||
value={query}
|
||||
placeholder="Search products"
|
||||
/>
|
||||
<SearchResults query={query} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
72
examples/react/algolia/src/SearchResults.tsx
Normal file
72
examples/react/algolia/src/SearchResults.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import useAlgolia from './useAlgolia'
|
||||
|
||||
type Product = {
|
||||
name: string
|
||||
shortDescription: string
|
||||
salePrice: number
|
||||
}
|
||||
|
||||
type SearchResultsProps = {
|
||||
query: string
|
||||
}
|
||||
|
||||
export default function SearchResults({ query = '' }: SearchResultsProps) {
|
||||
const {
|
||||
hits,
|
||||
isLoading,
|
||||
isFetching,
|
||||
status,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
} = useAlgolia<Product>({
|
||||
indexName: 'bestbuy',
|
||||
query,
|
||||
hitsPerPage: 5,
|
||||
staleTime: 1000 * 30, // 30s
|
||||
gcTime: 1000 * 60 * 15, // 15m
|
||||
})
|
||||
|
||||
if (!query) return null
|
||||
|
||||
if (isLoading) return <div className="loading">Loading...</div>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="search-status">
|
||||
Status: {status} {isFetching && <span>fetching...</span>}
|
||||
</div>
|
||||
<div>
|
||||
<div className="search-result">
|
||||
{hits && hits.length > 0 ? (
|
||||
hits.map((product) => (
|
||||
<li key={product.objectID} className="product">
|
||||
<span className="product-name">{product.name}</span>
|
||||
{product.shortDescription && (
|
||||
<>
|
||||
<br />
|
||||
<span className="product-description">
|
||||
{product.shortDescription}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
<span className="product-price">${product.salePrice}</span>
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<h3>No products found!</h3>
|
||||
)}
|
||||
</div>
|
||||
{hasNextPage && (
|
||||
<div className="search-more" onClick={() => fetchNextPage()}>
|
||||
more
|
||||
</div>
|
||||
)}
|
||||
{isFetchingNextPage && (
|
||||
<div className="search-status">Fetching next page...</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
37
examples/react/algolia/src/algolia.ts
Normal file
37
examples/react/algolia/src/algolia.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { searchClient } from '@algolia/client-search'
|
||||
import type { Hit } from '@algolia/client-search'
|
||||
|
||||
// From Algolia example
|
||||
// https://github.com/algolia/react-instantsearch
|
||||
const ALGOLIA_APP_ID = 'latency'
|
||||
const ALGOLIA_SEARCH_API_KEY = '6be0576ff61c053d5f9a3225e2a90f76'
|
||||
|
||||
type SearchOptions = {
|
||||
indexName: string
|
||||
query: string
|
||||
pageParam: number
|
||||
hitsPerPage: number
|
||||
}
|
||||
|
||||
export async function search<TData>({
|
||||
indexName,
|
||||
query,
|
||||
pageParam,
|
||||
hitsPerPage = 10,
|
||||
}: SearchOptions): Promise<{
|
||||
hits: Array<Hit<TData>>
|
||||
nextPage: number | undefined
|
||||
}> {
|
||||
const client = searchClient(ALGOLIA_APP_ID, ALGOLIA_SEARCH_API_KEY)
|
||||
|
||||
console.log('algolia:search', { indexName, query, pageParam, hitsPerPage })
|
||||
|
||||
const { hits, page, nbPages } = await client.searchSingleIndex<TData>({
|
||||
indexName,
|
||||
searchParams: { query, page: pageParam, hitsPerPage },
|
||||
})
|
||||
|
||||
const nextPage = page + 1 < nbPages ? page + 1 : undefined
|
||||
|
||||
return { hits, nextPage }
|
||||
}
|
||||
7
examples/react/algolia/src/index.tsx
Normal file
7
examples/react/algolia/src/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import ReactDOM from 'react-dom/client'
|
||||
|
||||
import App from './App'
|
||||
|
||||
const rootElement = document.getElementById('root')
|
||||
if (!rootElement) throw new Error('Missing #root element')
|
||||
ReactDOM.createRoot(rootElement).render(<App />)
|
||||
49
examples/react/algolia/src/styles.css
Normal file
49
examples/react/algolia/src/styles.css
Normal file
@@ -0,0 +1,49 @@
|
||||
.App {
|
||||
font-family: sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
padding-top: 20px;
|
||||
padding-left: 20px;
|
||||
color: darkmagenta;
|
||||
}
|
||||
|
||||
.search-status {
|
||||
padding-top: 20px;
|
||||
padding-left: 20px;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.search-result {
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.search-more {
|
||||
color: blue;
|
||||
padding-left: 20px;
|
||||
padding-top: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.product {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.product-description {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
34
examples/react/algolia/src/useAlgolia.ts
Normal file
34
examples/react/algolia/src/useAlgolia.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { skipToken, useInfiniteQuery } from '@tanstack/react-query'
|
||||
import { search } from './algolia'
|
||||
|
||||
export type UseAlgoliaOptions = {
|
||||
indexName: string
|
||||
query: string
|
||||
hitsPerPage?: number
|
||||
staleTime?: number
|
||||
gcTime?: number
|
||||
}
|
||||
|
||||
export default function useAlgolia<TData>({
|
||||
indexName,
|
||||
query,
|
||||
hitsPerPage = 10,
|
||||
staleTime,
|
||||
gcTime,
|
||||
}: UseAlgoliaOptions) {
|
||||
const queryInfo = useInfiniteQuery({
|
||||
queryKey: ['algolia', indexName, query, hitsPerPage],
|
||||
queryFn: query
|
||||
? ({ pageParam }) =>
|
||||
search<TData>({ indexName, query, pageParam, hitsPerPage })
|
||||
: skipToken,
|
||||
initialPageParam: 0,
|
||||
getNextPageParam: (lastPage) => lastPage.nextPage,
|
||||
staleTime,
|
||||
gcTime,
|
||||
})
|
||||
|
||||
const hits = queryInfo.data?.pages.map((page) => page.hits).flat()
|
||||
|
||||
return { ...queryInfo, hits }
|
||||
}
|
||||
24
examples/react/algolia/tsconfig.json
Normal file
24
examples/react/algolia/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
6
examples/react/algolia/vite.config.ts
Normal file
6
examples/react/algolia/vite.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
Reference in New Issue
Block a user