diff --git a/src/hooks/useMergedState.ts b/src/hooks/useMergedState.ts index 88c27c60..69130ada 100644 --- a/src/hooks/useMergedState.ts +++ b/src/hooks/useMergedState.ts @@ -30,6 +30,11 @@ const useUpdateEffect: typeof React.useEffect = (callback, deps) => { }, []); }; +/** We only think `undefined` is empty */ +function hasValue(value: any) { + return value !== undefined; +} + /** * Similar to `useState` but will use props value if provided. * Note that internal use rc-util `useState` hook. @@ -50,10 +55,10 @@ export default function useMergedState( let finalValue: T = undefined; let source: Source; - if (value !== undefined) { + if (hasValue(value)) { finalValue = value; source = Source.PROP; - } else if (defaultValue !== undefined) { + } else if (hasValue(defaultValue)) { finalValue = typeof defaultValue === 'function' ? (defaultValue as any)() @@ -70,9 +75,8 @@ export default function useMergedState( return [finalValue, source, finalValue]; }); - const postMergedValue = postState - ? postState(mergedValue[0]) - : mergedValue[0]; + const chosenValue = hasValue(value) ? value : mergedValue[0]; + const postMergedValue = postState ? postState(chosenValue) : chosenValue; // ======================= Sync ======================= useUpdateEffect(() => { diff --git a/tests/hooks.test.js b/tests/hooks.test.js index 7136e5a0..15a0bef4 100644 --- a/tests/hooks.test.js +++ b/tests/hooks.test.js @@ -201,6 +201,9 @@ describe('hooks', () => { setMergedValue(v => v + 1); setMergedValue(v => v + 1); }} + onMouseEnter={() => { + setMergedValue(1); + }} > {mergedValue} @@ -217,6 +220,8 @@ describe('hooks', () => { expect(onChange).not.toHaveBeenCalled(); // Click update + rerender(); + fireEvent.mouseEnter(container.querySelector('span')); fireEvent.click(container.querySelector('span')); expect(container.textContent).toEqual('3'); expect(onChange).toHaveBeenCalledWith(3, 1); @@ -251,6 +256,31 @@ describe('hooks', () => { fireEvent.mouseEnter(container.querySelector('span')); expect(onChange).toHaveBeenCalledWith(2, 1); }); + + it('should alway use option value', () => { + const onChange = jest.fn(); + + const Test = ({ value }) => { + const [mergedValue, setMergedValue] = useMergedState(undefined, { + value, + onChange, + }); + return ( + { + setMergedValue(12); + }} + > + {mergedValue} + + ); + }; + + const { container } = render(); + fireEvent.click(container.querySelector('span')); + + expect(container.textContent).toBe('1'); + }); }); describe('useLayoutEffect', () => {