useQueryParams
URL 쿼리 νλΌλ―Έν°λ₯Ό μ μΈμ μΌλ‘ κ΄λ¦¬νλ React Hookμ λλ€.
μΈμ μ¬μ©νλμ?
- π κ²μ/νν°λ§ (νμ΄μ§λ€μ΄μ , μ λ ¬)
- π URL 곡μ (μν μ μ§)
- β¬ οΈ λΈλΌμ°μ λ€λ‘κ°κΈ° μ§μ
- π ν/λ·° μν κ΄λ¦¬
μ£Όμ νΉμ§
1. νμ μμ μ±
μ€ν€λ§ κΈ°λ° νμλ‘ νμ μλ μΆλ‘
2. μ μΈμ API
Zod μ€νμΌμ λΉλ ν¨ν΄
3. SSR μ§μ
Next.js λ± SSR νκ²½μμ μμ νκ² λμ
4. νλ μμν¬ μ΄λν°
Browser, Next.js, React Router λ± λ€μν νκ²½ μ§μ
κΈ°λ³Έ μ¬μ©λ²
import {
useQueryParams,
parseAsInteger,
parseAsString,
parseAsBoolean,
parseAsStringEnum,
} from '@frontend-toolkit-js/hooks';
const sortOptions = ['latest', 'oldest', 'popular'] as const;
function ProductList() {
const { page, search, sort, showSoldOut, setParams } = useQueryParams({
page: parseAsInteger.withDefault(1),
search: parseAsString.withDefault(''),
sort: parseAsStringEnum(sortOptions).withDefault('latest'),
showSoldOut: parseAsBoolean.withDefault(false),
});
return (
<div>
{/* νμ¬ κ° μ¬μ© */}
<p>νμ¬ νμ΄μ§: {page}</p>
<p>κ²μμ΄: {search}</p>
{/* κ° μ
λ°μ΄νΈ */}
<button onClick={() => setParams({ page: page + 1 })}>
λ€μ νμ΄μ§
</button>
{/* μ¬λ¬ κ° λμ μ
λ°μ΄νΈ */}
<button onClick={() => setParams({ search: '', page: 1 })}>
κ²μ μ΄κΈ°ν
</button>
</div>
);
}Parsers
κΈ°λ³Έ νμ
| Parser | νμ | μμ |
|---|---|---|
parseAsString | string | ?name=hello β 'hello' |
parseAsInteger | number | ?page=5 β 5 |
parseAsFloat | number | ?price=19.99 β 19.99 |
parseAsBoolean | boolean | ?active=true β true |
λ μ§
| Parser | νμ | μμ |
|---|---|---|
parseAsIsoDate | Date | ?date=2024-01-15 β Date |
parseAsIsoDateTime | Date | ?date=2024-01-15T09:30:00Z β Date |
λ³΅ν© νμ
| Parser | νμ | μμ |
|---|---|---|
parseAsStringEnum(values) | T | ?sort=latest β 'latest' |
parseAsLiteral(values) | T | parseAsStringEnumμ λ³μΉ |
parseAsJson<T>() | T | ?data={"a":1} β { a: 1 } |
λ°°μ΄
| Parser | νμ | μμ |
|---|---|---|
parseAsArray(parser) | T[] | ?tags=a,b,c β ['a', 'b', 'c'] |
parseAsNativeArray(parser) | T[] | ?id=1&id=2 β [1, 2] |
Default Values
.withDefault()λ₯Ό μ¬μ©νλ©΄:
- 쿼리 νλΌλ―Έν°κ° μμ λ κΈ°λ³Έκ° μ¬μ©
- νμ
μ΄
T | nullμμTλ‘ λ³κ²½ (null μ κ±°)
// withDefault μμ΄: string | null
const { name } = useQueryParams({
name: parseAsString,
});
// withDefault μ¬μ©: string
const { name } = useQueryParams({
name: parseAsString.withDefault(''),
});History Options
// replace (κΈ°λ³Έκ°): νμ€ν 리μ μμ΄μ§ μμ
setParams({ page: 2 });
setParams({ page: 2 }, { history: 'replace' });
// push: νμ€ν 리μ μΆκ° (λ€λ‘κ°κΈ°λ‘ μ΄μ μν 볡μ κ°λ₯)
setParams({ page: 2 }, { history: 'push' });| μ΅μ | μ€λͺ | μ¬μ© μ¬λ‘ |
|---|---|---|
replace | νμ¬ νμ€ν 리 νλͺ© κ΅μ²΄ | νν° λ³κ²½, νμ΄μ§λ€μ΄μ |
push | μ νμ€ν 리 νλͺ© μΆκ° | ν μ ν, μ€μν μν λ³κ²½ |
Framework Adapters
νλ μμν¬λ³ μ΄λν°λ₯Ό μ 곡ν©λλ€. Provider ν¨ν΄μΌλ‘ μ± μ μμμ μ¬μ©ν μ μμ΅λλ€.
Next.js App Router
// app/providers.tsx
'use client';
import { Suspense, type ReactNode } from 'react';
import { QueryParamsProvider } from '@frontend-toolkit-js/hooks';
import { useNextAppAdapter } from '@frontend-toolkit-js/hooks/useQueryParams/adapters/next-app';
function QueryParamsProviderInner({ children }: { children: ReactNode }) {
const adapter = useNextAppAdapter();
return (
<QueryParamsProvider adapter={adapter}>
{children}
</QueryParamsProvider>
);
}
export function Providers({ children }: { children: ReactNode }) {
return (
<Suspense fallback={null}>
<QueryParamsProviderInner>{children}</QueryParamsProviderInner>
</Suspense>
);
}// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Note:
useSearchParamsλ₯Ό μ¬μ©νλ―λ‘Suspenseλ‘ κ°μΈμΌ ν©λλ€.
Next.js Pages Router
// _app.tsx
import { QueryParamsProvider } from '@frontend-toolkit-js/hooks';
import { useNextPagesAdapter } from '@frontend-toolkit-js/hooks/useQueryParams/adapters/next-pages';
function MyApp({ Component, pageProps }) {
const adapter = useNextPagesAdapter({ shallow: true });
return (
<QueryParamsProvider adapter={adapter}>
<Component {...pageProps} />
</QueryParamsProvider>
);
}React Router v6+
// App.tsx
import { BrowserRouter } from 'react-router-dom';
import { QueryParamsProvider } from '@frontend-toolkit-js/hooks';
import { useReactRouterAdapter } from '@frontend-toolkit-js/hooks/useQueryParams/adapters/react-router';
function Providers({ children }) {
const adapter = useReactRouterAdapter();
return (
<QueryParamsProvider adapter={adapter}>
{children}
</QueryParamsProvider>
);
}
function App() {
return (
<BrowserRouter>
<Providers>
{/* λΌμ°νΈ */}
</Providers>
</BrowserRouter>
);
}μ΄λν° μ΅μ
| μ΄λν° | μ΅μ | κΈ°λ³Έκ° | μ€λͺ |
|---|---|---|---|
useNextAppAdapter | scroll | false | URL λ³κ²½ μ μ€ν¬λ‘€ μ΄κΈ°ν |
useNextPagesAdapter | scroll | false | URL λ³κ²½ μ μ€ν¬λ‘€ μ΄κΈ°ν |
shallow | true | getServerSideProps μ¬μ€ν λ°©μ§ | |
useReactRouterAdapter | preventScrollReset | false | μ€ν¬λ‘€ μμΉ μ μ§ |
Provider ν¨ν΄
Providerλ₯Ό μ¬μ©νλ©΄ μ± μ μμμ μ΄λν°λ₯Ό 곡μ ν μ μμ΅λλ€.
// Provider μ€μ ν, μ»΄ν¬λνΈμμ adapter μλ΅ κ°λ₯
const { page } = useQueryParams({
page: parseAsInteger.withDefault(1),
});
// adapter μ΅μ
λΆνμ!Provider μμ΄ μ§μ μ΄λν°λ₯Ό μ λ¬ν μλ μμ΅λλ€:
import { createQueryParamsBrowserAdapter } from '@frontend-toolkit-js/hooks';
const { page } = useQueryParams(
{ page: parseAsInteger.withDefault(1) },
{ adapter: createQueryParamsBrowserAdapter() }
);API Reference
useQueryParams(schema, options?)
Parameters
| μ΄λ¦ | νμ | μ€λͺ |
|---|---|---|
schema | Record<string, Parser> | νμ μ€ν€λ§ κ°μ²΄ |
options.adapter | QueryParamsAdapter | 컀μ€ν μ΄λν° (Provider μ¬μ© μ μλ΅ κ°λ₯) |
Returns
| μ΄λ¦ | νμ | μ€λͺ |
|---|---|---|
[key] | T | μ€ν€λ§μ μ μλ κ° νλΌλ―Έν° κ° |
setParams | (params, options?) => void | νλΌλ―Έν° μ λ°μ΄νΈ ν¨μ |
setParams(params, options?)
| μ΅μ | νμ | κΈ°λ³Έκ° | μ€λͺ |
|---|---|---|---|
history | 'push' | 'replace' | 'replace' | νμ€ν 리 μ²λ¦¬ λ°©μ |
μ£Όμμ¬ν
β οΈ SSR νκ²½μμ Hydration
SSR νκ²½μμλ μλ²μ ν΄λΌμ΄μΈνΈμ μ΄κΈ° κ°μ΄ λ€λ₯Ό μ μμ΅λλ€. μ΄λν°κ° μ΄λ₯Ό μλμΌλ‘ μ²λ¦¬ν©λλ€.
β οΈ λ°°μ΄ νμ μ ν
// μ½€λ§ κ΅¬λΆ (κΆμ₯): ?tags=a,b,c
parseAsArray(parseAsString)
// λ€μ΄ν°λΈ λ°©μ: ?id=1&id=2&id=3
parseAsNativeArray(parseAsInteger)β οΈ Provider μμΉ
Next.js App Routerμμλ Suspense λ΄λΆμ Providerλ₯Ό λ°°μΉν΄μΌ ν©λλ€.
// β
μ¬λ°λ₯Έ λ°©λ²
<Suspense fallback={null}>
<QueryParamsProviderInner>
{children}
</QueryParamsProviderInner>
</Suspense>TypeScript
λͺ¨λ νμ μ΄ μλμΌλ‘ μΆλ‘ λ©λλ€.
const { page, search, tags } = useQueryParams({
page: parseAsInteger.withDefault(1),
search: parseAsString.withDefault(''),
tags: parseAsArray(parseAsString).withDefault([]),
});
page; // number (not null)
search; // string (not null)
tags; // string[] (not null)withDefault μμ΄ μ¬μ©νλ©΄ nullμ΄ ν¬ν¨λ©λλ€:
const { page } = useQueryParams({
page: parseAsInteger,
});
page; // number | nullκ΄λ ¨ λ¬Έμ
- useFunnel - λ¨κ³λ³ UI νλ¦ κ΄λ¦¬