useControllableValue
A hook to manage a controllable value
Loading...
Installation
npx shadcn@latest add @hooks/use-controllable-valuepnpm dlx shadcn@latest add @hooks/use-controllable-valueyarn dlx shadcn@latest add @hooks/use-controllable-valuebun x shadcn@latest add @hooks/use-controllable-valueCopy and paste the following code into your project.
import { useMemo, useRef } from 'react'
type noop = (this: any, ...args: any[]) => any
type PickFunction<T extends noop> = (
this: ThisParameterType<T>,
...args: Parameters<T>
) => ReturnType<T>
export function useMemoizedFn<T extends noop>(fn: T) {
const fnRef = useRef<T>(fn)
// why not write `fnRef.current = fn`?
// https://github.com/alibaba/hooks/issues/728
fnRef.current = useMemo<T>(() => fn, [fn])
const memoizedFn = useRef<PickFunction<T>>(undefined)
if (!memoizedFn.current) {
memoizedFn.current = function (this, ...args) {
return fnRef.current.apply(this, args)
}
}
return memoizedFn.current
}import { useCallback, useState } from 'react'
export function useUpdate() {
const [, setState] = useState({})
return useCallback(() => setState({}), [])
}import { isFunction } from 'es-toolkit/predicate'
import { useMemo, useRef } from 'react'
import { useMemoizedFn } from '@/registry/hooks/use-memoized-fn'
import { useUpdate } from '@/registry/hooks/use-update'
import type { SetStateAction } from 'react'
export interface Options<T> {
defaultValue?: T
defaultValuePropName?: string
valuePropName?: string
trigger?: string
}
export type Props = Record<string, any>
export interface StandardProps<T> {
value: T
defaultValue?: T
onChange: (val: T) => void
}
function useControllableValue<T = any>(
props: StandardProps<T>,
): [T, (v: SetStateAction<T>) => void]
function useControllableValue<T = any>(
props?: Props,
options?: Options<T>,
): [T, (v: SetStateAction<T>, ...args: any[]) => void]
function useControllableValue<T = any>(
defaultProps?: Props,
options: Options<T> = {},
) {
const props = defaultProps ?? {}
const {
defaultValue,
defaultValuePropName = 'defaultValue',
valuePropName = 'value',
trigger = 'onChange',
} = options
const value = props[valuePropName] as T
const isControlled = Object.prototype.hasOwnProperty.call(
props,
valuePropName,
)
const initialValue = useMemo(() => {
if (isControlled) {
return value
}
if (Object.prototype.hasOwnProperty.call(props, defaultValuePropName)) {
return props[defaultValuePropName]
}
return defaultValue
}, [])
const stateRef = useRef(initialValue)
if (isControlled) {
stateRef.current = value
}
const update = useUpdate()
const setState = (v: SetStateAction<T>, ...args: any[]) => {
const r = isFunction(v) ? v(stateRef.current) : v
if (!isControlled) {
stateRef.current = r
update()
}
if (props[trigger]) {
props[trigger](r, ...args)
}
}
return [stateRef.current, useMemoizedFn(setState)] as const
}
export { useControllableValue }API
export interface Options<T> {
defaultValue?: T
defaultValuePropName?: string
valuePropName?: string
trigger?: string
}
export type Props = Record<string, any>
export interface StandardProps<T> {
value: T
defaultValue?: T
onChange: (val: T) => void
}
/**
* A hook to manage a controllable value
* @param props - The props object containing value, defaultValue, and onChange
*/
function useControllableValue<T = any>(
props: StandardProps<T>,
): [T, (v: SetStateAction<T>) => void]
function useControllableValue<T = any>(
props?: Props,
options?: Options<T>,
): [T, (v: SetStateAction<T>, ...args: any[]) => void]Credits
Last updated on