Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/state-hook-returns.md
Original file line number Diff line number Diff line change
@@ -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<string>('token');
const { count, increment, decrement, reset, setCount } = useCounter(0);

// after
const [isOpen, { setTrue, setFalse, toggle }] = useBooleanState(false);
const [token, { setValue, refresh }] = useStorageState<string>('token');
const [count, { increment, decrement, reset, setCount }] = useCounter(0);
```

`useToggle` keeps its `[value, toggle]` signature and now declares its return type, so React 18 consumers no longer see the React 19 `ActionDispatch` type.
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ Never initialize state with browser API calls (causes hydration mismatch).
### Hook Return Value Convention

- **Single value**: `useDebounce<T>(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

Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ Never initialize state with browser API calls (causes hydration mismatch).
### Hook Return Value Convention

- **Single value**: `useDebounce<T>(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

Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/skills/react-simplikit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Backticks in this table mark catalog entries only.
| [`useSafeAreaInset`](references/useSafeAreaInset.md) | `useSafeAreaInset` is a React hook that tracks safe area inset changes. |
| [`useScrollDirection`](references/useScrollDirection.md) | `useScrollDirection` is a React hook that detects scroll direction. |
| [`useSet`](references/useSet.md) | A React hook that manages a Set as state. |
| [`useStorageState`](references/useStorageState.md) | `useStorageState` is a React that functions like `useState` but persists the state value in browser storage. |
| [`useStorageState`](references/useStorageState.md) | `useStorageState` is a React hook that functions like `useState` but persists the state value in browser storage. |
| [`useThrottle`](references/useThrottle.md) | `useThrottle` is a React hook that creates a throttled version of a callback function. |
| [`useThrottledCallback`](references/useThrottledCallback.md) | `useThrottledCallback` is a React hook that returns a throttled version of the provided callback function. |
| [`useThrottledValue`](references/useThrottledValue.md) | `useThrottledValue` is a React hook that returns a throttled copy of the given value. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -28,8 +23,8 @@ function useBooleanState(

<Interface
name=""
type="readonly [state: boolean, setTrue: () => void, setFalse: () => void, toggle: () => void]"
description="A tuple containing:"
type="readonly [state: boolean, actions: BooleanStateActions]"
description="A tuple containing the state and actions to change it."
:nested="[
{
name: 'state',
Expand All @@ -38,19 +33,19 @@ function useBooleanState(
description: 'The current state value.',
},
{
name: 'setTrue',
name: 'actions.setTrue',
type: '() => void',
required: false,
description: 'A function to set the state to <code>true</code>.',
},
{
name: 'setFalse',
name: 'actions.setFalse',
type: '() => void',
required: false,
description: 'A function to set the state to <code>false</code>.',
},
{
name: 'toggle',
name: 'actions.toggle',
type: '() => void',
required: false,
description: 'A function to toggle the state.',
Expand All @@ -61,6 +56,6 @@ function useBooleanState(
## Example

```tsx
const [open, openBottomSheet, closeBottomSheet, toggleBottomSheet] =
const [open, { setTrue: openBottomSheet, setFalse: closeBottomSheet }] =
useBooleanState(false);
```
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function useCounter(
<Interface
name=""
type="UseCounterReturn"
description="An object with count value and control functions."
description="A tuple containing the count and actions to change it."
:nested="[
{
name: 'count',
Expand All @@ -64,29 +64,29 @@ function useCounter(
description: 'The current count value.',
},
{
name: 'increment',
name: 'actions.increment',
type: '() => void',
required: false,
description: 'A function to increment the count.',
},
{
name: 'decrement',
name: 'actions.decrement',
type: '() => void',
required: false,
description: 'A function to decrement the count.',
},
{
name: 'reset',
name: 'actions.reset',
type: '() => void',
required: false,
description: 'A function to reset the count to the initial value.',
},
{
name: 'setCount',
name: 'actions.setCount',
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.',
},
]"
/>
Expand All @@ -97,7 +97,7 @@ function useCounter(
import { useCounter } from 'react-simplikit';

function ShoppingCart() {
const { count, increment, decrement, reset } = useCounter(1, {
const [count, { increment, decrement, reset }] = useCounter(1, {
min: 1,
max: 10,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,8 +11,7 @@ function useStorageState<T>(
options?: Object
): readonly [
state: Serializable<T> | undefined,
setState: (value: SetStateAction<Serializable<T> | undefined>) => void,
refreshState: () => void,
actions: StorageStateActions<Serializable<T> | undefined>,
];
```

Expand Down Expand Up @@ -63,8 +62,8 @@ function useStorageState<T>(

<Interface
name=""
type="readonly [state: Serializable<T> | undefined, setState: (value: SetStateAction<Serializable<T> | undefined>) => void, refreshState: () => void]"
description="A tuple:"
type="readonly [state: Serializable<T> | undefined, actions: StorageStateActions<Serializable<T> | undefined>]"
description="A tuple containing the state and actions to change it."
:nested="[
{
name: 'state',
Expand All @@ -73,13 +72,13 @@ function useStorageState<T>(
description: 'The current state value retrieved from storage.',
},
{
name: 'setState',
name: 'actions.setValue',
type: '(value: SetStateAction<Serializable<T> | undefined>) => void',
required: false,
description: 'A function to update and persist the state.',
},
{
name: 'refreshState',
name: 'actions.refresh',
type: '() => void',
required: false,
description: 'A function to refresh the state from storage.',
Expand All @@ -94,12 +93,12 @@ function useStorageState<T>(
import { useStorageState } from 'react-simplikit';

function Counter() {
const [count, setCount] = useStorageState<number>('counter', {
const [count, { setValue }] = useStorageState<number>('counter', {
defaultValue: 0,
});

return (
<button onClick={() => setCount(prev => prev + 1)}>Count: {count}</button>
<button onClick={() => setValue(prev => prev + 1)}>Count: {count}</button>
);
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ Proporciona funciones para establecer el estado en `true`, establecerlo en `fals
```ts
function useBooleanState(
initialValue: boolean | (() => boolean) = false
): readonly [
state: boolean,
setTrue: () => void,
setFalse: () => void,
toggle: () => void,
];
): readonly [state: boolean, actions: BooleanStateActions];
```

### Parámetros
Expand All @@ -28,8 +23,8 @@ function useBooleanState(

<Interface
name=""
type="readonly [state: boolean, setTrue: () => void, setFalse: () => void, toggle: () => void]"
description="Una tupla que contiene:"
type="readonly [state: boolean, actions: BooleanStateActions]"
description="Una tupla con el estado y las acciones para cambiarlo."
:nested="[
{
name: 'state',
Expand All @@ -38,19 +33,19 @@ function useBooleanState(
description: 'El valor actual del estado.',
},
{
name: 'setTrue',
name: 'actions.setTrue',
type: '() => void',
required: false,
description: 'Una función para establecer el estado en <code>true</code>.',
},
{
name: 'setFalse',
name: 'actions.setFalse',
type: '() => void',
required: false,
description: 'Una función para establecer el estado en <code>false</code>.',
},
{
name: 'toggle',
name: 'actions.toggle',
type: '() => void',
required: false,
description: 'Una función para alternar el estado.',
Expand All @@ -61,6 +56,6 @@ function useBooleanState(
## Ejemplo

```tsx
const [open, openBottomSheet, closeBottomSheet, toggleBottomSheet] =
const [open, { setTrue: openBottomSheet, setFalse: closeBottomSheet }] =
useBooleanState(false);
```
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@
```ts
function useBooleanState(
initialValue: boolean | (() => boolean) = false
): readonly [
state: boolean,
setTrue: () => void,
setFalse: () => void,
toggle: () => void,
];
): readonly [state: boolean, actions: BooleanStateActions];
```

### パラメータ
Expand All @@ -28,8 +23,8 @@ function useBooleanState(

<Interface
name=""
type="readonly [state: boolean, setTrue: () => void, setFalse: () => void, toggle: () => void]"
description="以下を含むタプル。"
type="readonly [state: boolean, actions: BooleanStateActions]"
description="状態と、状態を変更するアクションを含むタプル。"
:nested="[
{
name: 'state',
Expand All @@ -38,19 +33,19 @@ function useBooleanState(
description: '現在の状態の値。',
},
{
name: 'setTrue',
name: 'actions.setTrue',
type: '() => void',
required: false,
description: '状態を <code>true</code> にする関数。',
},
{
name: 'setFalse',
name: 'actions.setFalse',
type: '() => void',
required: false,
description: '状態を <code>false</code> にする関数。',
},
{
name: 'toggle',
name: 'actions.toggle',
type: '() => void',
required: false,
description: '状態を切り替える関数。',
Expand All @@ -61,6 +56,6 @@ function useBooleanState(
## 使用例

```tsx
const [open, openBottomSheet, closeBottomSheet, toggleBottomSheet] =
const [open, { setTrue: openBottomSheet, setFalse: closeBottomSheet }] =
useBooleanState(false);
```
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@
```ts
function useBooleanState(
initialValue: boolean | (() => boolean) = false
): readonly [
state: boolean,
setTrue: () => void,
setFalse: () => void,
toggle: () => void,
];
): readonly [state: boolean, actions: BooleanStateActions];
```

### 파라미터
Expand All @@ -27,8 +22,8 @@ function useBooleanState(

<Interface
name=""
type="readonly [state: boolean, setTrue: () => void, setFalse: () => void, toggle: () => void]"
description="다음 값들을 포함하는 튜플이에요:"
type="readonly [state: boolean, actions: BooleanStateActions]"
description="상태와 상태를 바꾸는 액션을 담은 튜플이에요."
:nested="[
{
name: 'state',
Expand All @@ -37,19 +32,19 @@ function useBooleanState(
description: '현재 상태 값이에요.',
},
{
name: 'setTrue',
name: 'actions.setTrue',
type: '() => void',
required: false,
description: '상태를 <code>true</code>로 설정하는 함수예요.',
},
{
name: 'setFalse',
name: 'actions.setFalse',
type: '() => void',
required: false,
description: '상태를 <code>false</code>로 설정하는 함수예요.',
},
{
name: 'toggle',
name: 'actions.toggle',
type: '() => void',
required: false,
description: '상태를 토글하는 함수예요.',
Expand All @@ -60,6 +55,6 @@ function useBooleanState(
## 예시

```tsx
const [open, openBottomSheet, closeBottomSheet, toggleBottomSheet] =
const [open, { setTrue: openBottomSheet, setFalse: closeBottomSheet }] =
useBooleanState(false);
```
Loading
Loading