From 4d684e44d55115ddadf1552dbd3bf5b344a30fb3 Mon Sep 17 00:00:00 2001 From: Albert Yu Date: Wed, 20 Sep 2023 22:34:55 +0800 Subject: [PATCH] Add warning when switching between controlled and uncontrolled --- .../src/utils/useControllableReducer.test.tsx | 2 +- .../src/utils/useControllableReducer.ts | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/mui-base/src/utils/useControllableReducer.test.tsx b/packages/mui-base/src/utils/useControllableReducer.test.tsx index 24e9a9afd7faf3..2df18d9e520108 100644 --- a/packages/mui-base/src/utils/useControllableReducer.test.tsx +++ b/packages/mui-base/src/utils/useControllableReducer.test.tsx @@ -168,7 +168,7 @@ describe('useControllableReducer', () => { initialState: { make: 'Mazda', model: '3', productionYear: 2022 }, }; - function TestComponent(props: { make: string }) { + function TestComponent(props: { make?: string }) { const [state] = useControllableReducer({ ...reducerParameters, controlledProps: { diff --git a/packages/mui-base/src/utils/useControllableReducer.ts b/packages/mui-base/src/utils/useControllableReducer.ts index 790719a937279b..c5abfaca0c5ba3 100644 --- a/packages/mui-base/src/utils/useControllableReducer.ts +++ b/packages/mui-base/src/utils/useControllableReducer.ts @@ -156,6 +156,33 @@ export function useControllableReducer< actionContext, } = parameters; + const controlledPropsRef = React.useRef(controlledProps); + + if (process.env.NODE_ENV !== 'production') { + // eslint-disable-next-line react-hooks/rules-of-hooks + React.useEffect(() => { + Object.keys(controlledProps).forEach((key) => { + if ( + (controlledPropsRef.current as Record)[key] !== undefined && + (controlledProps as Record)[key] === undefined + ) { + console.error( + 'MUI: useControllableReducer is changing a controlled prop to be uncontrolled', + ); + } + + if ( + (controlledPropsRef.current as Record)[key] === undefined && + (controlledProps as Record)[key] !== undefined + ) { + console.error( + 'MUI: useControllableReducer is changing an uncontrolled prop to be controlled', + ); + } + }); + }, [controlledProps]); + } + // The reducer that is passed to React.useReducer is wrapped with a function that augments the state with controlled values. const reducerWithControlledState = React.useCallback( (state: State, action: ActionWithContext) => {