UNPKG

state-in-url

Version:

Store state in URL as in object, types and structure are preserved, with TS validation. Same API as React.useState, wthout any hasssle or boilerplate. Next.js@14-15, react-router@6-7, and remix@2.

837 lines (634 loc) 30.5 kB
<!-- i18n:start --> [English](./README.md) · [简体中文](./README.zh-CN.md) · 日本語 · [한국어](./README.ko.md) · [Русский](./README.ru.md) · [Español](./README.es.md) · [Português (BR)](./README.pt-BR.md) · [Français](./README.fr.md) · [Tiếng Việt](./README.vi.md) <!-- i18n:meta locale=ja source=README.md source-blob=bbf45a10aae9e88f68b27d84f51ee3e7e8a1e436 status=translated --> <!-- i18n:end --> <div align="center"> <img src="/packages/example-nextjs14/public/Logo_symbol.png" alt="state-in-url ロゴ" width="120px" /> # State in url </div> <div align="center"> </div> <div align="center"> [![Available for hire](https://img.shields.io/badge/available%20for%20hire-senior%20react%20engineer-2ea44f?style=flat-square)](https://asmyshlyaev177.dev) [![npm](https://img.shields.io/npm/v/state-in-url.svg)](https://www.npmjs.com/package/state-in-url) ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/73be54068b7f41b0b74a252579ac09ec)](https://app.codacy.com/gh/asmyshlyaev177/state-in-url/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=master) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/73be54068b7f41b0b74a252579ac09ec)](https://app.codacy.com/gh/asmyshlyaev177/state-in-url/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://github.com/asmyshlyaev177/state-in-url/) [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)]([https://github.com/semantic-release/semantic-release](https://github.com/asmyshlyaev177/state-in-url)) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/asmyshlyaev177/state-in-url/badge)](https://scorecard.dev/viewer/?uri=github.com/asmyshlyaev177/state-in-url) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9679/badge)](https://www.bestpractices.dev/projects/9679) [![license](https://img.shields.io/github/license/asmyshlyaev177/state-in-url.svg?style=flat-square)](https://github.com/asmyshlyaev177/state-in-url/blob/master/LICENSE) <!-- or by embedding this in your HTML: <a href="https://www.bestpractices.dev/projects/9679"><img src="https://www.bestpractices.dev/projects/9679/badge"></a> --> </div> <div align="center"> <h4 align="center">バグを発見した場合や、機能のリクエストがあれば、遠慮なく issue を作成してください</h4> ![Demo-gif](https://github.com/asmyshlyaev177/state-in-url/blob/master/assets/Demo-gif.gif) # デモ <a href="https://state-in-url.dev" target="_blank">デモ</a> | <a href="https://state-in-url.netlify.app/" target="_blank">ミラーリンク</a> <a href="https://github.com/asmyshlyaev177/state-in-url/blob/master/Limits.md" target="_blank">URI サイズ制限、<b>最大 12KB</b> までは安全です</a> <hr /> プロジェクトを支援するには、<a href="#"><img src="https://raw.githubusercontent.com/acervenky/animated-github-badges/master/assets/starbadge.gif" width="25" height="25"></a> を付けて <a href="https://github.com/asmyshlyaev177" target="_blank">フォロー</a>してください! [ディスカッション](https://github.com/asmyshlyaev177/state-in-url/discussions/1)でフィードバックやご意見をいただけると嬉しいです 役に立ったら共有してください。 [X.com](https://twitter.com/intent/tweet?&url=https://github.com/asmyshlyaev177/state-in-url) [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://github.com/asmyshlyaev177/state-in-url) [FB](https://www.facebook.com/sharer.php?u=https://github.com/asmyshlyaev177/state-in-url) [VK](http://vk.com/share.php?url=https://github.com/asmyshlyaev177/state-in-url) <hr /> </div> [コードだけ見せて!](#useurlstate) ## なぜ `state-in-url` を使うのか? 任意のユーザー状態をクエリパラメータに保存します。ブラウザの URL に JSON があるイメージです。データの型と構造を保持したまま、たとえば数値は文字列ではなく数値として、日付は日付としてデコードされ、オブジェクトや配列もサポートされます。 非常にシンプルで高速、そして静的 TypeScript バリデーション付き。ディープリンク(別名 URL 同期)が簡単になります。 Next.js と react-router 用の `useUrlState` フックと、その他あらゆる JS 向けのヘルパーを備えています。 現代のブラウザは巨大な URL をサポートしており、ユーザーはクエリ文字列を気にしません(全選択してコピー/ペーストするワークフローです)。 本来の目的どおりに、クエリ文字列を状態管理に使うときが来ました。 このライブラリは、そうした退屈な作業をすべて代行します。 このライブラリは NUQS の有力な代替になります。 ### ユースケース - 保存していないユーザーフォームやページフィルターを URL に保存 - URL と React の状態を同期 - URI に触れずに、関連のないクライアントコンポーネント間でデータを同期 - アプリケーション状態を含む共有可能な URL(ディープリンキング、URL 状態同期) - ページ再読み込みをまたいだ簡単な状態の永続化 ### 特徴 - 🧩 **シンプル**: プロバイダー、リデューサー、ボイラープレート、新しい概念は不要。`React.useState` に似た API - 📘 **TypeScript バリデーション/自動補完**: 状態はただのオブジェクトで、TypeScript 定義に従って IDE/テストで自動的に静的検証されます - ✨ **複雑なデータ**: ネストしたオブジェクト、日付、配列。JSON と同じように動き、URL の中に置けます - ☂ **デフォルト値**: URL にパラメータがない場合はデフォルト値を提供します - ⌨ **整理されている**: 可能な値はすべて最初に定義され、存在しないキーを取得するのを防ぎます - **互換性**: サードパーティのクエリパラメータはそのまま保持します - **柔軟**: 同じページで複数の状態オブジェクトを使用できます。別のキーを使うだけです - **高速**: リレンダーは最小限。大きなオブジェクトのエンコード/デコードは約 [1ms](https://github.com/asmyshlyaev177/state-in-url/blob/87c8c7c995c5cd7d9e7aa039c30bfe64b24abe4b/packages/urlstate/encoder/encoder.test.ts#L185) です - **サーバーサイドレンダリング**: サーバーコンポーネントで使用可能。Next.js 141516 をサポート - **軽量**: 依存関係ゼロ。ライブラリは 2KB 未満 - **DX**: 優れた開発者体験。ドキュメント、JSDoc コメント、サンプル付き - **フレームワークの柔軟性**: `Next.js` と `react-router` 用のフック、その他のフレームワークやピュア JS で使うためのヘルパー - **十分にテスト済み**: [Chrome/Firefox/Safari 向けのユニットテストと Playwright テスト](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml) - **寛容なライセンス**: MIT ## 目次 <!-- toc:start --> - [State in url](#state-in-url) - [デモ](#デモ) - [なぜ `state-in-url` を使うのか?](#なぜ-state-in-url-を使うのか) - [ユースケース](#ユースケース) - [特徴](#特徴) - [目次](#目次) - [インストール](#インストール) - [1. パッケージのインストール](#1-パッケージのインストール) - [2. tsconfig.json の編集](#2-tsconfigjson-の編集) - [AI コーディングエージェントと使う](#ai-コーディングエージェントと使う) - [useUrlState](#useurlstate) - [Next.js 向け useUrlState フック](#nextjs-向け-useurlstate-フック) - [使用例](#使用例) - [基本](#基本) - [サーバーサイドレンダリングで使用](#サーバーサイドレンダリングで使用) - [`layout` コンポーネントでフックを使用](#layout-コンポーネントでフックを使用) - [任意の状態の形を使用(非推奨)](#任意の状態の形を使用非推奨) - [Remix.js 向け useUrlState フック](#remixjs-向け-useurlstate-フック) - [例](#例) - [React-Router 向け useUrlState フック](#react-router-向け-useurlstate-フック) - [例](#例-1) - [レシピ](#レシピ) - [状態の一部を便利に扱うカスタムフック](#状態の一部を便利に扱うカスタムフック) - [複雑な状態の形を使用](#複雑な状態の形を使用) - [状態だけを更新して URL には手動で同期](#状態だけを更新して-url-には手動で同期) - [その他のフックとヘルパー](#その他のフックとヘルパー) - [他のルーター向け `useUrlStateBase` フック](#他のルーター向け-useurlstatebase-フック) - [React.js 向け `useSharedState` フック](#reactjs-向け-usesharedstate-フック) - [React.js 向け `useLinkProps` フック](#reactjs-向け-uselinkprops-フック) - [React.js 向け `useUrlEncode` フック](#reactjs-向け-useurlencode-フック) - [`encodeState` と `decodeState` ヘルパー](#encodestate-と-decodestate-ヘルパー) - [`encode` と `decode` ヘルパー](#encode-と-decode-ヘルパー) - [ベストプラクティス](#ベストプラクティス) - [注意点](#注意点) - [その他](#その他) - [貢献、またはローカルでの実行](#貢献またはローカルでの実行) - [ロードマップ](#ロードマップ) - [連絡先とサポート](#連絡先とサポート) - [変更履歴](#変更履歴) - [言及](#言及) - [ライセンス](#ライセンス) - [採用のご依頼](#採用のご依頼) - [インスピレーション](#インスピレーション) <!-- toc:end --> ## インストール ### 1. パッケージのインストール ```sh # npm npm install --save state-in-url # yarn yarn add state-in-url # pnpm pnpm add state-in-url ``` ### 2. tsconfig.json の編集 `tsconfig.json` の `compilerOptions` に `"moduleResolution": "Bundler"`、または `"moduleResolution": "Node16"`、または `"moduleResolution": "NodeNext"` を設定します。 `"module": "ES2022"` または `"module": "ESNext"` の設定が必要になる場合があります。 ## AI コーディングエージェントと使う `state-in-url` は [@tanstack/intent](https://tanstack.com/intent/latest/docs/overview) 向けのスキルファイルを同梱しています。AI エージェント(Claude Code、Cursor、Copilot、Codex など)がライブラリ使用時の正しいパターンを読み込み、よくあるミスを避けられます。`state-in-url` をインストールした後、プロジェクトで一度実行してください: ```sh npx @tanstack/intent@latest install ``` これにより、インストール済みのエージェントが `node_modules/state-in-url/skills/` からスキルを検出するようになります。利用可能なスキルは `npx @tanstack/intent@latest list` で一覧できます。 ## useUrlState 初期状態をパラメータとして受け取り、状態オブジェクト、URL を更新するコールバック、状態だけを更新するコールバックを返すメインフックです。 同じ `state` オブジェクトを使うすべてのコンポーネントは自動的に同期されます。 ### Next.js 向け useUrlState フック [完全な API ドキュメント](packages/urlstate/next/useUrlState) [React-Router の例](#react-router-向け-useurlstate-フック) #### 使用例 ##### 基本 1. デフォルト値付きで状態の形を定義します ```typescript // userState.ts // デフォルトと異なる値を持つパラメータだけが URL に入ります。 export const userState: UserState = { name: '', age: 0 } // `Interface` ではなく `Type` を使うこと! type UserState = { name: string, age: number } ``` 2. インポートして使用します ```typescript 'use client' import { useUrlState } from 'state-in-url/next'; import { userState } from './userState'; function MyComponent() { // `replace` 引数を渡せます。`setUrl` が `router.push` と `router.replace` のどちらを使うかを制御し、デフォルトは replace=true // サーバーコンポーネントから `searchParams` を渡せます。サーバーコンポーネントで何かを取得する必要がある場合は `useHistory: false` を渡してください const { urlState, setUrl, setState } = useUrlState(userState); return ( <div> // URL が空の場合、urlState.name は `userState` のデフォルト値を返します <input value={urlState.name} // React.useState と同じ API。例: setUrl(currVal => currVal + 1) onChange={(ev) => setUrl({ name: ev.target.value }) } /> <input value={urlState.age} onChange={(ev) => setUrl({ age: +ev.target.value }) } /> <input value={urlState.name} onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }} // 状態は即座に更新しつつ、変更は必要に応じて URL に同期できます onBlur={() => setUrl()} /> <button onClick={() => setUrl((_, initial) => initial)}> Reset </button> </div> ) } ``` ##### サーバーサイドレンダリングで使用 <details> <Summary>例</Summary> ```typescript export default async function Home({ searchParams }: { searchParams: object }) { return ( <Form searchParams={searchParams} /> ) } // Form.tsx 'use client' import React from 'react'; import { useUrlState } from 'state-in-url/next'; import { form } from './form'; const Form = ({ searchParams }: { searchParams: object }) => { const { urlState, setState, setUrl } = useUrlState(form, { searchParams }); } ``` </details> ##### `layout` コンポーネントでフックを使用 <details> <Summary>例</Summary> これは難しい部分です。app router を使う nextjs はサーバー側から searchParams にアクセスできません。middleware を使う回避策はありますが、美しくはなく、nextjs の更新後に動作しなくなる可能性があります。 ```typescript // 適切な `layout.tsx` に追加 export const runtime = 'edge'; // middleware.ts import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; export function middleware(request: NextRequest) { const url = request.url?.includes('_next') ? null : request.url; const sp = url?.split?.('?')?.[1] || ''; const response = NextResponse.next(); if (url !== null) { response.headers.set('searchParams', sp); } return response; } // 対象の layout コンポーネント import { headers } from 'next/headers'; import { decodeState } from 'state-in-url/encodeState'; export default async function Layout({ children, }: { children: React.ReactNode; }) { const sp = headers().get('searchParams') || ''; return ( <div> <Comp1 searchParams={decodeState(sp, stateShape)} /> {children} </div> ); } ``` </details> ##### 任意の状態の形を使用(非推奨) <details> <Summary>例</Summary> ```typescript 'use client' import { useUrlState } from 'state-in-url/next'; const someObj = {}; function SettingsComponent() { const { urlState, setUrl, setState } = useUrlState<object>(someObj); } ``` </details> ### Remix.js 向け useUrlState フック API は Next.js 版と同じですが、[NavigateOptions](https://github.com/remix-run/react-router/blob/bc693ed9f39170bda13b9e1b282fb8e9d5925f66/packages/react-router/lib/context.ts#L99) 型のオプションを渡せます。 [API ドキュメント](packages/urlstate/remix/useUrlState) #### 例 ```typescript export const form: Form = { name: '', age: undefined, agree_to_terms: false, tags: [], }; type Form = { name: string; age?: number; agree_to_terms: boolean; tags: { id: string; value: { text: string; time: Date } }[]; }; ``` ```typescript import { useUrlState } from 'state-in-url/remix'; import { form } from './form'; function TagsComponent() { const { urlState, setUrl, setState } = useUrlState(form); const onChangeTags = React.useCallback( (tag: (typeof tags)[number]) => { setUrl((curr) => ({ ...curr, tags: curr.tags.find((t) => t.id === tag.id) ? curr.tags.filter((t) => t.id !== tag.id) : curr.tags.concat(tag), })); }, [setUrl], ); return ( <div> <Field text="Tags"> <div className="flex flex-wrap gap-2"> {tags.map((tag) => ( <Tag active={!!urlState.tags.find((t) => t.id === tag.id)} text={tag.value.text} onClick={() => onChangeTags(tag)} key={tag.id} /> ))} </div> </Field> <input value={urlState.name} onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }} // 状態は即座に更新しつつ、変更は必要に応じて URL に同期できます onBlur={() => setUrl()} /> </div> ); } const tags = [ { id: '1', value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') }, }, { id: '2', value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') }, }, { id: '3', value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') }, }, ]; ``` [サンプルコード](packages/example-remix2/app/routes/Form-for-test.tsx) ### React-Router 向け useUrlState フック API は Next.js 版と同じですが、[NavigateOptions](https://github.com/remix-run/react-router/blob/bc693ed9f39170bda13b9e1b282fb8e9d5925f66/packages/react-router/lib/context.ts#L99) 型のオプションを渡せます。 [API ドキュメント](packages/urlstate/react-router/useUrlState) #### 例 ```typescript export const form: Form = { name: '', age: undefined, agree_to_terms: false, tags: [], }; type Form = { name: string; age?: number; agree_to_terms: boolean; tags: { id: string; value: { text: string; time: Date } }[]; }; ``` ```typescript import { useUrlState } from 'state-in-url/react-router'; // react-router v6 の場合 // import { useUrlState } from 'state-in-url/react-router6'; import { form } from './form'; function TagsComponent() { const { urlState, setUrl, setState } = useUrlState(form); const onChangeTags = React.useCallback( (tag: (typeof tags)[number]) => { setUrl((curr) => ({ ...curr, tags: curr.tags.find((t) => t.id === tag.id) ? curr.tags.filter((t) => t.id !== tag.id) : curr.tags.concat(tag), })); }, [setUrl], ); return ( <div> <Field text="Tags"> <div className="flex flex-wrap gap-2"> {tags.map((tag) => ( <Tag active={!!urlState.tags.find((t) => t.id === tag.id)} text={tag.value.text} onClick={() => onChangeTags(tag)} key={tag.id} /> ))} </div> </Field> <input value={urlState.name} onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }} // 状態は即座に更新しつつ、変更は必要に応じて URL に同期できます onBlur={() => setUrl()} /> </div> ); } const tags = [ { id: '1', value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') }, }, { id: '2', value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') }, }, { id: '3', value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') }, }, ]; ``` [サンプルコード](packages/example-react-router6/src/Form-for-test.tsx) ## レシピ ##### 状態の一部を便利に扱うカスタムフック <details> <Summary>例</Summary> ```typescript 'use client'; import React from 'react'; import { useUrlState } from 'state-in-url/next'; const form: Form = { name: '', age: undefined, agree_to_terms: false, tags: [], }; type Form = { name: string; age?: number; agree_to_terms: boolean; tags: {id: string; value: {text: string; time: Date } }[]; }; export const useFormState = ({ searchParams }: { searchParams?: object }) => { const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, { searchParams, }); // 最初のナビゲーションは新しい履歴エントリを push します // 以降はすべてそのエントリを replace します // これにより履歴は 2 エントリだけになります - ['/url', '/url?key=param'] const replace = React.useRef(false); const setUrl = React.useCallback(( state: Parameters<typeof setUrlBase>[0], opts?: Parameters<typeof setUrlBase>[1] ) => { setUrlBase(state, { replace: replace.current, ...opts }); replace.current = true; }, [setUrlBase]); return { urlState, setUrl, resetUrl: reset }; }; ``` </details> <hr /> ##### 複雑な状態の形を使用 <details> <Summary>例</Summary> ```typescript export const form: Form = { name: '', age: undefined, agree_to_terms: false, tags: [], }; type Form = { name: string; age?: number; agree_to_terms: boolean; tags: { id: string; value: { text: string; time: Date } }[]; }; ``` ```typescript 'use client' import { useUrlState } from 'state-in-url/next'; import { form } from './form'; function TagsComponent() { // `urlState` は Form 型から推論されます! const { urlState, setUrl } = useUrlState(form); const onChangeTags = React.useCallback( (tag: (typeof tags)[number]) => { setUrl((curr) => ({ ...curr, tags: curr.tags.find((t) => t.id === tag.id) ? curr.tags.filter((t) => t.id !== tag.id) : curr.tags.concat(tag), })); }, [setUrl], ); return ( <div> <Field text="Tags"> <div className="flex flex-wrap gap-2"> {tags.map((tag) => ( <Tag active={!!urlState.tags.find((t) => t.id === tag.id)} text={tag.value.text} onClick={() => onChangeTags(tag)} key={tag.id} /> ))} </div> </Field> </div> ); } const tags = [ { id: '1', value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') }, }, { id: '2', value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') }, }, { id: '3', value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') }, }, ]; ``` [デモページのサンプルコード](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs15/src/app/Form.tsx) </details> ##### 状態だけを更新して URL には手動で同期 <details> <Summary>例</Summary> ```typescript const timer = React.useRef(0 as unknown as NodeJS.Timeout); React.useEffect(() => { clearTimeout(timer.current); timer.current = setTimeout(() => { // 参照ではなく内容で状態を比較し、新しい値の場合だけ更新を実行します setUrl(urlState); }, 500); return () => { clearTimeout(timer.current); }; }, [urlState, setUrl]); ``` `onBlur` で状態を同期する方が実際の用途に近いでしょう。 ```typescript <input onBlur={() => updateUrl()} .../> ``` </details> ## その他のフックとヘルパー ### 他のルーター向け `useUrlStateBase` フック react-router や tanstack router など、他のルーターで独自の `useUrlState` フックを作成するためのフックです。 [API ドキュメント](packages/urlstate/useUrlStateBase) ### React.js 向け `useSharedState` フック 任意の React コンポーネント間で状態を共有するフック。Next.js と Vite でテスト済みです。 ```typescript 'use client' import { useSharedState } from 'state-in-url'; export const someState = { name: '' }; function SettingsComponent() { const { state, setState } = useSharedState(someState); } ``` [API ドキュメント](packages/urlstate/useSharedState/README.ja.md) ### React.js 向け `useLinkProps` フック 別のルートを指すリンクに状態を引き継ぐためのフックです。言語切り替えなどに使います。`setUrl` は常に現在のパスに書き込みますが、こちらは違います。 ```tsx 'use client' import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useLinkProps } from 'state-in-url/useLinkProps'; export const form = { name: '' }; function LanguagePicker() { const linkProps = useLinkProps(form, useRouter().push); return <Link {...linkProps('/de/pricing')}>Deutsch</Link>; } ``` マークアップには素の `href` が残るため、クローラーと `hreflang` は正規の URL を参照します。状態はクリック時に読み取られます。 [API ドキュメント](packages/urlstate/useLinkProps/README.ja.md) ### React.js 向け `useUrlEncode` フック [API ドキュメント](packages/urlstate/useUrlEncode/README.ja.md) ### `encodeState` と `decodeState` ヘルパー [API ドキュメント](packages/urlstate/encodeState/README.ja.md) ### `encode` と `decode` ヘルパー [API ドキュメント](packages/urlstate/encoder/README.ja.md) ## ベストプラクティス - 状態の形を定数として定義する - 型安全性と自動補完を高めるために TypeScript を使う - 機密情報(SSN、API キーなど)を URL パラメータに保存しない - 読みやすい TS エラーを得るには、この [拡張機能](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) を使う 状態の一部に対応する状態フックを作成し、アプリケーション全体で再利用できます。例: ```Typescript type UserState = { name: string; age: number; other: { id: string, value: number }[] }; const userState = { name: '', age: 0, other: [], }; export const useUserState = () => { const { urlState, setUrl, reset } = useUrlState(userState); // その他のロジック // 別のページに移動したときにクエリパラメータをリセット React.useEffect(() => { return reset }, []) return { userState: urlState, setUserState: setUrl };; } ``` ## 注意点 1. シリアライズ可能な値しか渡せません。`Function`、`BigInt`、`Symbol` は動作せず、`ArrayBuffer` のようなものもおそらく動作しません。JSON にシリアライズできるものはすべて動作します。 2. Vercel サーバーはヘッダー(クエリ文字列など)のサイズを **14KB** に制限しているため、URL の状態は約 5000 語未満に保ってください。<https://vercel.com/docs/errors/URL_TOO_LONG> 3. app router 付きの `next.js` 14/15/16 でテスト済みです。pages をサポートする予定はありません。 ## その他 ### 貢献、またはローカルでの実行 [貢献ガイド](CONTRIBUTING.md) を参照 ## ロードマップ - [x] `Next.js` 向けフック - [x] `react-router` 向けフック - [x] `remix` 向けフック - [ ] `svelte` 向けフック - [ ] `astro` 向けフック - [ ] ハッシュに状態を保存するフック? ## 連絡先とサポート - バグ報告、機能リクエスト、質問には [GitHub issue](https://github.com/asmyshlyaev177/state-in-url/issues) を作成してください ## [変更履歴](CHANGELOG.md) ## 言及 - [This Week in React 209](https://thisweekinreact.com/newsletter/209) - [JavaScript Weekly](https://javascriptweekly.com/issues/741) - [This Week in React 240](https://thisweekinreact.com/newsletter/240) ## ライセンス このプロジェクトは [MIT ライセンス](LICENSE) の下で提供されています。 ## 採用のご依頼 **Aleksandr Smyshliaev** です。このライブラリの唯一の作者でありメンテナです。 シニアフロントエンドエンジニア(React / Next.js / TypeScript、8 年以上)。現在**フルタイムのリモート勤務を募集しています**。 このライブラリは、私の得意分野を凝縮したものです。乱雑なブラウザプリミティブに対する型付き API、依存関係ゼロ、そしていくつもの React メジャーバージョンにわたる Next.js、Remix、React Router での安定性。 - **得意分野** — コンポーネントライブラリ、状態管理、リファクタリングに耐えるテストスイート。 - **その他のプロジェクト** — [react-horizontal-scrolling-menu](https://github.com/asmyshlyaev177/react-horizontal-scrolling-menu) (週間約 84k インストール)、 [test-proxy-recorder](https://github.com/asmyshlyaev177/test-proxy-recorder) (Playwright 向けの record/replay)、 [llm-queue](https://github.com/asmyshlyaev177/llm-queue)。 - **所在地** — ジョージア・トビリシ(GMT+4)。CET と完全に重なります。契約事業体として登録済みなので、B2B 契約に employer-of-record の手配は不要です。 - **連絡先** — [asmyshlyaev177.dev](https://asmyshlyaev177.dev) · [asmyshlyaev177@gmail.com](mailto:asmyshlyaev177@gmail.com) · [LinkedIn](https://linkedin.com/in/asmyshlyaev177) · Telegram @asmyshlyaev177 ## インスピレーション [NUQS](https://github.com/47ng/nuqs) [Vue で URL に状態を保存する](https://dev.to/jacobandrewsky/using-url-to-store-state-in-vue-275c) [URL に状態を保存する](https://antonz.org/storing-state/) [NextJS useSearchParams](https://nextjs.org/docs/app/api-reference/functions/use-search-params)