Skip to content

Commit

Permalink
chore(react-utilities): simplifies useControllableState hook internals (
Browse files Browse the repository at this point in the history
  • Loading branch information
bsunderhus authored Apr 26, 2023
1 parent 9d6394b commit 557ffca
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "chore: simplifies useControllableState hook internals",
"packageName": "@fluentui/react-utilities",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ export type UseControllableStateOptions<State> = {
initialState: State;
};

function isFactoryDispatch<State>(newState: React.SetStateAction<State>): newState is (prevState: State) => State {
return typeof newState === 'function';
}

/**
* @internal
*
Expand All @@ -42,40 +38,21 @@ function isFactoryDispatch<State>(newState: React.SetStateAction<State>): newSta
export const useControllableState = <State>(
options: UseControllableStateOptions<State>,
): [State, React.Dispatch<React.SetStateAction<State>>] => {
const isControlled = useIsControlled(options.state);
const initialState = typeof options.defaultState === 'undefined' ? options.initialState : options.defaultState;
const [internalState, setInternalState] = React.useState<State>(initialState);

const state = isControlled ? (options.state as State) : internalState;

const stateRef = React.useRef(state);
React.useEffect(() => {
stateRef.current = state;
}, [state]);

// To match the behavior of the setter returned by React.useState, this callback's identity
// should never change. This means it MUST NOT directly reference variables that can change.
const setState = React.useCallback((newState: React.SetStateAction<State>) => {
// React dispatch can use a factory
// https://reactjs.org/docs/hooks-reference.html#functional-updates
if (isFactoryDispatch(newState)) {
stateRef.current = newState(stateRef.current);
} else {
stateRef.current = newState;
}

setInternalState(stateRef.current);
}, []);

return [state, setState];
return useIsControlled(options.state) ? [options.state, noop] : [internalState, setInternalState];
};

function noop() {
/* noop */
}

/**
* Helper hook to handle previous comparison of controlled/uncontrolled
* Prints an error when isControlled value switches between subsequent renders
* @returns - whether the value is controlled
*/
const useIsControlled = (controlledValue: unknown) => {
const useIsControlled = <V>(controlledValue?: V): controlledValue is V => {
const [isControlled] = React.useState<boolean>(() => controlledValue !== undefined);

if (process.env.NODE_ENV !== 'production') {
Expand Down

0 comments on commit 557ffca

Please sign in to comment.