/* State that represents a value that may or may not be controlled by a prop. * * Uses `useRefState` for synchronous updates, so `onChange` events can be * raised with their original event objects (e.g. using `CactusChangeEvent`). * * When controlled, the prop value is always used for rendering but the state * isn't updated till rendering finishes to avoid concurrency issues (React 18). * State updates might operate on "old" state in the interim, but since such * updates generally arise from user input, and the users haven't even seen the * "new" state yet, this is probably the correct behavior. * * If the state DOES change during render, the controlled value is dropped * under the assumption that another render is already pending as a result * of the concurrent state change. */ import { Reducer, useEffect } from 'react' import useRefState, { Initializer, SyncDispatch } from './useRefState' export type Normalizer
= (props: P, lastState: S) => S // Advanced mapping: drops any keys that don't map to values of the state type. type Keyof
= keyof { [K in keyof P as S extends P[K] ? K : never]: unknown }
// Unlike `useRefState`, this requires an initializer because (presumably)
// the fact that it falls back to internal state if the prop is undefined
// means the value shouldn't be undefined (unless explicitly part of the type).
interface UseControllableValue {
/* Simple version: pass the prop name to extract the value from props. */
(props: P, key: Keyof
, initArg: Initializer): [S, SyncDispatch]
(props: P, key: Keyof
, reducer: Reducer, initial: S): [
S,
SyncDispatch
]
(
props: P,
key: Keyof
,
reducer: Reducer,
initArg: I,
initializer: (i: I) => S
): [S, SyncDispatch]
/* Normalized version: pass a function that extracts the value from props. */
(props: P, normalize: Normalizer
, initArg: Initializer): [S, SyncDispatch]
(props: P, normalize: Normalizer
, reducer: Reducer, initial: S): [
S,
SyncDispatch
]
(
props: P,
normalize: Normalizer
,
reducer: Reducer,
initArg: I,
initializer: (i: I) => S
): [S, SyncDispatch]
}
// Uses generic types externally, but concrete types internally to validate logic.
type StateType = string
type ActionType = void
type Props = { value?: StateType }
type KeyType = Keyof