Skip to content
Merged
5 changes: 5 additions & 0 deletions .changeset/hip-pugs-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-simplikit': minor
---

Add `useDebouncedState` and `useThrottledState`, which manage state and return debounced and throttled setters, respectively. Both hooks support `leading` and `trailing` options and cancel pending updates on unmount or when timing options change.
2 changes: 2 additions & 0 deletions packages/plugin/skills/react-simplikit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ Backticks in this table mark catalog entries only.
| [`useCounter`](references/useCounter.md) | `useCounter` is a React hook that manages a numeric counter state with increment, decrement, and reset capabilities. |
| [`useDebounce`](references/useDebounce.md) | `useDebounce` is a React hook that returns a debounced version of the provided callback function. |
| [`useDebouncedCallback`](references/useDebouncedCallback.md) | `useDebouncedCallback` is a React hook that returns a debounced version of the provided callback function. |
| [`useDebouncedState`](references/useDebouncedState.md) | `useDebouncedState` is a React hook that manages state and returns a debounced setter. |
| [`useDebouncedValue`](references/useDebouncedValue.md) | `useDebouncedValue` is a React hook that returns a debounced copy of the given value. |
| [`useDoubleClick`](references/useDoubleClick.md) | `useDoubleClick` is a React hook that differentiates between single and double click events. |
| [`useGeolocation`](references/useGeolocation.md) | `useGeolocation` is a React hook that retrieves and tracks the user's geographical location. |
Expand Down Expand Up @@ -110,6 +111,7 @@ Backticks in this table mark catalog entries only.
| [`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. |
| [`useThrottledState`](references/useThrottledState.md) | `useThrottledState` is a React hook that manages state and returns a throttled setter. |
| [`useThrottledValue`](references/useThrottledValue.md) | `useThrottledValue` is a React hook that returns a throttled copy of the given value. |
| [`useTimeout`](references/useTimeout.md) | `useTimeout` is a React hook that executes a callback function after a specified delay. |
| [`useToggle`](references/useToggle.md) | `useToggle` is a React hook that simplifies managing a boolean state. |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# useDebouncedState

`useDebouncedState` is a React hook that manages state and returns a debounced setter.
With the default options, the state updates `debounceMs` milliseconds after the last setter call.
It is useful for applying only the last value from a series of rapid update requests, such as search input.

The initial value is returned as is on the first render and on the server. No update is scheduled
on mount, so with `leading: true` the first setter call after mount is applied immediately.
If both `leading` and `trailing` are `false`, the state never updates.

Pending updates are cancelled when the component unmounts or when `debounceMs`, `leading`, or
`trailing` changes.

## Interface

```ts
function useDebouncedState<T>(
initialValue: T,
debounceMs: number,
options?: DebounceOptions
): [state: T, setState: Dispatch<SetStateAction<T>>];
```

### Parameters

<Interface
required
name="initialValue"
type="T"
description="The initial state value, used only on the first render."
/>

<Interface
required
name="debounceMs"
type="number"
description="The number of milliseconds to wait after the last setter call before updating."
/>

<Interface
name="options"
type="DebounceOptions"
description="Configuration options for debounce behavior."
:nested="[
{
name: 'options.leading',
type: 'boolean',
required: false,
defaultValue: 'false',
description:
'If <code>true</code>, the first setter call after an idle period is applied immediately.',
},
{
name: 'options.trailing',
type: 'boolean',
required: false,
defaultValue: 'true',
description:
'If <code>true</code>, the last value is applied after <code>debounceMs</code> milliseconds.',
},
]"
/>

### Return Value

<Interface
name=""
type="[state: T, setState: Dispatch<SetStateAction<T>>]"
description="A tuple:"
:nested="[
{
name: 'state',
type: 'T',
required: false,
description: 'The current state value.',
},
{
name: 'setState',
type: 'Dispatch<SetStateAction<T>>',
required: false,
description:
'A debounced setter that accepts the next state value or an updater function.',
},
]"
/>

## Example

```tsx
import { useDebouncedState } from 'react-simplikit';

function SearchInput() {
const [query, setQuery] = useDebouncedState('', 300);

return (
<>
<label>
Search
<input onChange={event => setQuery(event.target.value)} />
</label>
<output aria-live="polite">{query}</output>
</>
);
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# useThrottledState

`useThrottledState` is a React hook that manages state and returns a throttled setter.
With the default options, the first setter call is applied immediately, and subsequent calls
are throttled using `throttleMs`, with the last pending value applied on the trailing edge.
It is useful for limiting state updates from frequent events, such as scrolling.

The initial value is returned as is on the first render and on the server. No update is scheduled
on mount, so with `leading: true` the first setter call after mount is applied immediately.
If both `leading` and `trailing` are `false`, the state never updates.

Pending updates are cancelled when the component unmounts or when `throttleMs`, `leading`, or
`trailing` changes.

## Interface

```ts
function useThrottledState<T>(
initialValue: T,
throttleMs: number,
options?: ThrottleOptions
): [state: T, setState: Dispatch<SetStateAction<T>>];
```

### Parameters

<Interface
required
name="initialValue"
type="T"
description="The initial state value, used only on the first render."
/>

<Interface
required
name="throttleMs"
type="number"
description="The length of the throttle window in milliseconds."
/>

<Interface
name="options"
type="ThrottleOptions"
description="Configuration options for throttle behavior."
:nested="[
{
name: 'options.leading',
type: 'boolean',
required: false,
defaultValue: 'true',
description:
'If <code>true</code>, allows an immediate update at the start of a throttle window.',
},
{
name: 'options.trailing',
type: 'boolean',
required: false,
defaultValue: 'true',
description:
'If <code>true</code>, allows a pending update to run after the delay with the latest value.',
},
]"
/>

### Return Value

<Interface
name=""
type="[state: T, setState: Dispatch<SetStateAction<T>>]"
description="A tuple:"
:nested="[
{
name: 'state',
type: 'T',
required: false,
description: 'The current state value.',
},
{
name: 'setState',
type: 'Dispatch<SetStateAction<T>>',
required: false,
description:
'A throttled setter that accepts the next state value or an updater function.',
},
]"
/>

## Example

```tsx
import { useThrottledState } from 'react-simplikit';

function ScrollPosition() {
const [scrollTop, setScrollTop] = useThrottledState(0, 200);

return (
<div onScroll={e => setScrollTop(e.currentTarget.scrollTop)}>
<p>Scrolled {scrollTop}px</p>
</div>
);
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# useDebouncedState

`useDebouncedState` es un Hook de React que gestiona el estado y devuelve una función de actualización con debounce. Con las opciones predeterminadas, el estado se actualiza `debounceMs` milisegundos después de tu última llamada a la función de actualización. Es útil para aplicar solo el último valor de una serie de solicitudes de actualización rápidas, como las de un campo de búsqueda.

El valor inicial se devuelve sin cambios en el primer renderizado y en el servidor. No se programa ninguna actualización al montar el componente, por lo que, con `leading: true`, tu primera llamada a la función de actualización después del montaje se aplica de inmediato. Si tanto `leading` como `trailing` son `false`, el estado nunca se actualiza.

Las actualizaciones pendientes se cancelan cuando el componente se desmonta o cuando cambian `debounceMs`, `leading` o `trailing`.

## Interfaz

```ts
function useDebouncedState<T>(
initialValue: T,
debounceMs: number,
options?: DebounceOptions
): [state: T, setState: Dispatch<SetStateAction<T>>];
```

### Parámetros

<Interface
required
name="initialValue"
type="T"
description="El valor inicial del estado, que solo se utiliza en el primer renderizado."
/>

<Interface
required
name="debounceMs"
type="number"
description="El tiempo de espera en milisegundos desde tu última llamada a la función de actualización hasta que se actualiza el estado."
/>

<Interface
name="options"
type="DebounceOptions"
description="Opciones de configuración del comportamiento de debounce."
:nested="[
{
name: 'options.leading',
type: 'boolean',
required: false,
defaultValue: 'false',
description:
'Si es <code>true</code>, tu primera llamada a la función de actualización después de un período de inactividad se aplica de inmediato.',
},
{
name: 'options.trailing',
type: 'boolean',
required: false,
defaultValue: 'true',
description:
'Si es <code>true</code>, el último valor se aplica después de <code>debounceMs</code> milisegundos.',
},
]"
/>

### Valor de retorno

<Interface
name=""
type="[state: T, setState: Dispatch<SetStateAction<T>>]"
description="Una tupla:"
:nested="[
{
name: 'state',
type: 'T',
required: false,
description: 'El valor actual del estado.',
},
{
name: 'setState',
type: 'Dispatch<SetStateAction<T>>',
required: false,
description: 'Una función de actualización con debounce que acepta el siguiente valor del estado o una función de actualización.',
},
]"
/>

## Ejemplo

```tsx
import { useDebouncedState } from 'react-simplikit';

function SearchInput() {
const [query, setQuery] = useDebouncedState('', 300);

return (
<>
<label>
Search
<input onChange={event => setQuery(event.target.value)} />
</label>
<output aria-live="polite">{query}</output>
</>
);
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useDebouncedState } from './useDebouncedState.ts';
Loading
Loading