From 1425afb086597d5d4a9f3861b67aa546de859b0d Mon Sep 17 00:00:00 2001 From: mnxmnz <48766355+mnxmnz@users.noreply.github.com> Date: Tue, 6 Oct 2026 23:40:32 +0900 Subject: [PATCH 1/3] refactor(useBooleanState, useStorageState, useCounter): return the value with an actions object --- .changeset/state-hook-returns.md | 19 +++++ AGENTS.md | 6 +- CLAUDE.md | 6 +- .../references/useBooleanState.md | 19 ++--- .../react-simplikit/references/useCounter.md | 12 +-- .../references/useStorageState.md | 15 ++-- .../useBooleanState/es/useBooleanState.md | 19 ++--- .../useBooleanState/ja/useBooleanState.md | 19 ++--- .../useBooleanState/ko/useBooleanState.md | 19 ++--- .../hooks/useBooleanState/useBooleanState.md | 19 ++--- .../useBooleanState/useBooleanState.spec.ts | 18 ++++- .../hooks/useBooleanState/useBooleanState.ts | 26 ++++--- .../zh-Hans/useBooleanState.md | 19 ++--- .../src/hooks/useCounter/es/useCounter.md | 12 +-- .../src/hooks/useCounter/ja/useCounter.md | 12 +-- .../src/hooks/useCounter/ko/useCounter.md | 12 +-- .../src/hooks/useCounter/useCounter.md | 12 +-- .../src/hooks/useCounter/useCounter.spec.ts | 78 +++++++++++-------- .../src/hooks/useCounter/useCounter.ts | 32 ++++---- .../hooks/useCounter/zh-Hans/useCounter.md | 12 +-- .../useStorageState/es/useStorageState.md | 15 ++-- .../useStorageState/ja/useStorageState.md | 15 ++-- .../useStorageState/ko/useStorageState.md | 15 ++-- .../hooks/useStorageState/useStorageState.md | 15 ++-- .../useStorageState/useStorageState.spec.ts | 30 ++++--- .../hooks/useStorageState/useStorageState.ts | 48 ++++++------ .../zh-Hans/useStorageState.md | 15 ++-- .../src/hooks/useToggle/useToggle.ts | 10 +-- 28 files changed, 287 insertions(+), 262 deletions(-) create mode 100644 .changeset/state-hook-returns.md diff --git a/.changeset/state-hook-returns.md b/.changeset/state-hook-returns.md new file mode 100644 index 00000000..c53cee1f --- /dev/null +++ b/.changeset/state-hook-returns.md @@ -0,0 +1,19 @@ +--- +'react-simplikit': major +--- + +`useBooleanState`, `useStorageState` and `useCounter` now return `[value, actions]`, matching `useList`, `useMap` and `useSet`. The actions object keeps the same reference across renders. + +```ts +// before +const [isOpen, setTrue, setFalse, toggle] = useBooleanState(false); +const [token, setToken, refreshToken] = useStorageState('token'); +const { count, increment, decrement, reset, setCount } = useCounter(0); + +// after +const [isOpen, { setTrue, setFalse, toggle }] = useBooleanState(false); +const [token, { setValue, refresh }] = useStorageState('token'); +const [count, { increment, decrement, reset, setCount }] = useCounter(0); +``` + +`useToggle` keeps its `[value, toggle]` signature, is now built on `useBooleanState`, and declares its return type so that React 18 consumers no longer see the React 19 `ActionDispatch` type. diff --git a/AGENTS.md b/AGENTS.md index 4e92374c..119d49b9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,9 +116,9 @@ Never initialize state with browser API calls (causes hydration mismatch). ### Hook Return Value Convention - **Single value**: `useDebounce(value, delay): T` -- **Tuple** (2 items): `useToggle(init): [boolean, () => void]` -- **Object** (3+ items): `usePagination(): { page, nextPage, prevPage }` -- **Object** also when the shape is expected to grow: `useKeyboardHeight(): { keyboardHeight }` +- **Tuple** (state + one action): `useToggle(init): [boolean, () => void]` +- **Tuple with an actions object** (state + two or more actions): `useBooleanState(init): [boolean, { setTrue, setFalse, toggle }]`, `useList`, `useCounter` +- **Object** for results that are not state + actions, and whenever the shape is expected to grow: browser measurements such as `useKeyboardHeight(): { keyboardHeight }` ## Testing diff --git a/CLAUDE.md b/CLAUDE.md index 2809c868..7332c8fd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,9 +89,9 @@ Never initialize state with browser API calls (causes hydration mismatch). ### Hook Return Value Convention - **Single value**: `useDebounce(value, delay): T` -- **Tuple** (state + action, 2 items): `useToggle(init): [boolean, () => void]` -- **Object** (3+ items): `usePagination(): { page, nextPage, prevPage }` -- **Object** also when the shape is expected to grow: browser measurements such as `useKeyboardHeight(): { keyboardHeight }` +- **Tuple** (state + one action): `useToggle(init): [boolean, () => void]` +- **Tuple with an actions object** (state + two or more actions): `useBooleanState(init): [boolean, { setTrue, setFalse, toggle }]`, `useList`, `useCounter` +- **Object** for results that are not state + actions, and whenever the shape is expected to grow: browser measurements such as `useKeyboardHeight(): { keyboardHeight }` ### Performance Patterns diff --git a/packages/plugin/skills/react-simplikit/references/useBooleanState.md b/packages/plugin/skills/react-simplikit/references/useBooleanState.md index 3ef27c3e..50443689 100644 --- a/packages/plugin/skills/react-simplikit/references/useBooleanState.md +++ b/packages/plugin/skills/react-simplikit/references/useBooleanState.md @@ -8,12 +8,7 @@ It provides functions to set the state to `true`, set it to `false`, and toggle ```ts function useBooleanState( initialValue: boolean | (() => boolean) = false -): readonly [ - state: boolean, - setTrue: () => void, - setFalse: () => void, - toggle: () => void, -]; +): readonly [state: boolean, actions: BooleanStateActions]; ``` ### Parameters @@ -28,8 +23,8 @@ function useBooleanState( number)) => void', required: false, description: - 'A function to set the count to a specific value or a function that returns a new value.', + 'A function to set the count, either to a value or with an updater function that receives the previous count and returns the next one.', }, ]" /> diff --git a/packages/plugin/skills/react-simplikit/references/useStorageState.md b/packages/plugin/skills/react-simplikit/references/useStorageState.md index f4efb065..851d5d84 100644 --- a/packages/plugin/skills/react-simplikit/references/useStorageState.md +++ b/packages/plugin/skills/react-simplikit/references/useStorageState.md @@ -1,6 +1,6 @@ # useStorageState -`useStorageState` is a React that functions like `useState` but persists the state value in browser storage. +`useStorageState` is a React hook that functions like `useState` but persists the state value in browser storage. The value is retained across page reloads and can be shared between tabs when using `localStorage`. ## Interface diff --git a/packages/react-simplikit/src/hooks/useCounter/es/useCounter.md b/packages/react-simplikit/src/hooks/useCounter/es/useCounter.md index 3c4286e7..58e727c5 100644 --- a/packages/react-simplikit/src/hooks/useCounter/es/useCounter.md +++ b/packages/react-simplikit/src/hooks/useCounter/es/useCounter.md @@ -86,7 +86,7 @@ function useCounter( type: '(value: number | ((prev: number) => number)) => void', required: false, description: - 'Una función para actualizar el contador con un valor específico o con una función que devuelve un nuevo valor.', + 'Una función para actualizar el contador con un valor específico o con una función que recibe el valor anterior y devuelve el siguiente.', }, ]" /> diff --git a/packages/react-simplikit/src/hooks/useCounter/ja/useCounter.md b/packages/react-simplikit/src/hooks/useCounter/ja/useCounter.md index 6716f3b8..3ed3033e 100644 --- a/packages/react-simplikit/src/hooks/useCounter/ja/useCounter.md +++ b/packages/react-simplikit/src/hooks/useCounter/ja/useCounter.md @@ -86,7 +86,7 @@ function useCounter( type: '(value: number | ((prev: number) => number)) => void', required: false, description: - '特定の値、または新しい値を返す関数を渡して、カウンターの値を設定する関数。', + '特定の値、または前のカウントを受け取って次の値を返す関数を渡して、カウンターの値を設定する関数。', }, ]" /> diff --git a/packages/react-simplikit/src/hooks/useCounter/ko/useCounter.md b/packages/react-simplikit/src/hooks/useCounter/ko/useCounter.md index 63d4dfe8..f3883246 100644 --- a/packages/react-simplikit/src/hooks/useCounter/ko/useCounter.md +++ b/packages/react-simplikit/src/hooks/useCounter/ko/useCounter.md @@ -85,7 +85,7 @@ function useCounter( type: '(value: number | ((prev: number) => number)) => void', required: false, description: - '카운트를 특정 값으로 설정하거나 새로운 값을 반환하는 함수예요.', + '카운트를 특정 값으로 설정하거나, 이전 카운트를 받아 다음 값을 반환하는 함수로 갱신하는 함수예요.', }, ]" /> diff --git a/packages/react-simplikit/src/hooks/useCounter/useCounter.md b/packages/react-simplikit/src/hooks/useCounter/useCounter.md index 54052aea..51f8edd9 100644 --- a/packages/react-simplikit/src/hooks/useCounter/useCounter.md +++ b/packages/react-simplikit/src/hooks/useCounter/useCounter.md @@ -86,7 +86,7 @@ function useCounter( type: '(value: number | ((prev: number) => number)) => void', required: false, description: - 'A function to set the count to a specific value or a function that returns a new value.', + 'A function to set the count, either to a value or with an updater function that receives the previous count and returns the next one.', }, ]" /> diff --git a/packages/react-simplikit/src/hooks/useCounter/useCounter.ts b/packages/react-simplikit/src/hooks/useCounter/useCounter.ts index fd6d744b..0568ec1e 100644 --- a/packages/react-simplikit/src/hooks/useCounter/useCounter.ts +++ b/packages/react-simplikit/src/hooks/useCounter/useCounter.ts @@ -43,7 +43,7 @@ const validateValue = (value: number, { min, max }: Omit void` - A function to increment the count; * - actions.decrement `() => void` - A function to decrement the count; * - actions.reset `() => void` - A function to reset the count to the initial value; - * - actions.setCount `(value: number | ((prev: number) => number)) => void` - A function to set the count to a specific value or a function that returns a new value; + * - actions.setCount `(value: number | ((prev: number) => number)) => void` - A function to set the count, either to a value or with an updater function that receives the previous count and returns the next one; * * @example * import { useCounter } from 'react-simplikit'; diff --git a/packages/react-simplikit/src/hooks/useCounter/zh-Hans/useCounter.md b/packages/react-simplikit/src/hooks/useCounter/zh-Hans/useCounter.md index 4c717cb2..dda11d5c 100644 --- a/packages/react-simplikit/src/hooks/useCounter/zh-Hans/useCounter.md +++ b/packages/react-simplikit/src/hooks/useCounter/zh-Hans/useCounter.md @@ -85,7 +85,7 @@ function useCounter( type: '(value: number | ((prev: number) => number)) => void', required: false, description: - '将计数设置为特定值,或设置为返回新值的函数的函数。', + '将计数设置为特定值,或通过接收上一个计数并返回下一个值的函数来更新计数。', }, ]" /> diff --git a/packages/react-simplikit/src/hooks/useStorageState/ko/useStorageState.md b/packages/react-simplikit/src/hooks/useStorageState/ko/useStorageState.md index df82c1ee..7ebaa27d 100644 --- a/packages/react-simplikit/src/hooks/useStorageState/ko/useStorageState.md +++ b/packages/react-simplikit/src/hooks/useStorageState/ko/useStorageState.md @@ -1,6 +1,6 @@ # useStorageState -리액트 훅으로, `useState`처럼 작동하지만 상태 값을 브라우저 저장소에 저장해요. 이 값은 페이지를 새로고침해도 유지되며, `localStorage`를 사용할 경우 탭 간에 공유될 수 있어요. +`useStorageState`는 `useState`처럼 작동하지만 상태 값을 브라우저 저장소에 저장하는 리액트 훅이에요. 이 값은 페이지를 새로고침해도 유지되며, `localStorage`를 사용할 경우 탭 간에 공유될 수 있어요. ## 인터페이스 diff --git a/packages/react-simplikit/src/hooks/useStorageState/useStorageState.md b/packages/react-simplikit/src/hooks/useStorageState/useStorageState.md index f4efb065..851d5d84 100644 --- a/packages/react-simplikit/src/hooks/useStorageState/useStorageState.md +++ b/packages/react-simplikit/src/hooks/useStorageState/useStorageState.md @@ -1,6 +1,6 @@ # useStorageState -`useStorageState` is a React that functions like `useState` but persists the state value in browser storage. +`useStorageState` is a React hook that functions like `useState` but persists the state value in browser storage. The value is retained across page reloads and can be shared between tabs when using `localStorage`. ## Interface diff --git a/packages/react-simplikit/src/hooks/useStorageState/useStorageState.ts b/packages/react-simplikit/src/hooks/useStorageState/useStorageState.ts index 06578f11..267f7461 100644 --- a/packages/react-simplikit/src/hooks/useStorageState/useStorageState.ts +++ b/packages/react-simplikit/src/hooks/useStorageState/useStorageState.ts @@ -68,7 +68,7 @@ const ensureSerializable = (value: T): SerializableGua /** * @description - * `useStorageState` is a React that functions like `useState` but persists the state value in browser storage. + * `useStorageState` is a React hook that functions like `useState` but persists the state value in browser storage. * The value is retained across page reloads and can be shared between tabs when using `localStorage`. * * @template T - The type of the stored value. diff --git a/packages/react-simplikit/src/hooks/useStorageState/zh-Hans/useStorageState.md b/packages/react-simplikit/src/hooks/useStorageState/zh-Hans/useStorageState.md index 3f4a458d..548705c0 100644 --- a/packages/react-simplikit/src/hooks/useStorageState/zh-Hans/useStorageState.md +++ b/packages/react-simplikit/src/hooks/useStorageState/zh-Hans/useStorageState.md @@ -88,7 +88,7 @@ function useStorageState( ## 示例 ```tsx -// Counter with persistent state +// 具有持久化状态的计数器 import { useStorageState } from 'react-simplikit'; function Counter() { From a403c7e065b9396d14e7d8d5418d666ffe3291a7 Mon Sep 17 00:00:00 2001 From: mnxmnz <48766355+mnxmnz@users.noreply.github.com> Date: Tue, 6 Oct 2026 23:46:22 +0900 Subject: [PATCH 3/3] fix(useToggle): keep the useReducer implementation to stay within the tree-shaking size gate --- .changeset/state-hook-returns.md | 2 +- packages/react-simplikit/src/hooks/useToggle/useToggle.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.changeset/state-hook-returns.md b/.changeset/state-hook-returns.md index c53cee1f..3062b7c2 100644 --- a/.changeset/state-hook-returns.md +++ b/.changeset/state-hook-returns.md @@ -16,4 +16,4 @@ const [token, { setValue, refresh }] = useStorageState('token'); const [count, { increment, decrement, reset, setCount }] = useCounter(0); ``` -`useToggle` keeps its `[value, toggle]` signature, is now built on `useBooleanState`, and declares its return type so that React 18 consumers no longer see the React 19 `ActionDispatch` type. +`useToggle` keeps its `[value, toggle]` signature and now declares its return type, so React 18 consumers no longer see the React 19 `ActionDispatch` type. diff --git a/packages/react-simplikit/src/hooks/useToggle/useToggle.ts b/packages/react-simplikit/src/hooks/useToggle/useToggle.ts index 0ed34902..b117c87a 100644 --- a/packages/react-simplikit/src/hooks/useToggle/useToggle.ts +++ b/packages/react-simplikit/src/hooks/useToggle/useToggle.ts @@ -1,4 +1,4 @@ -import { useBooleanState } from '../useBooleanState/index.ts'; +import { useReducer } from 'react'; /** * @description @@ -26,7 +26,7 @@ import { useBooleanState } from '../useBooleanState/index.ts'; * } */ export function useToggle(initialValue: boolean = false): [boolean, () => void] { - const [state, { toggle }] = useBooleanState(initialValue); - - return [state, toggle]; + return useReducer(toggle, initialValue); } + +const toggle = (state: boolean) => !state;