Repository: alibaba/hooks
Pull request #916 (2021-04-19 13:40:35)

enhance: improve type inference for useControllableValue


Patch:
diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts
index 2facbfeb..aebdfb03 100644
--- a/packages/hooks/src/index.ts
+++ b/packages/hooks/src/index.ts
@@ -59,7 +59,7 @@ import useTrackedEffect from './useTrackedEffect';
 import useUnmountedRef from './useUnmountedRef';
 import useExternal from './useExternal';
 
-const useControlledValue: typeof useControllableValue = function (...args) {
+const useControlledValue: typeof useControllableValue = function (...args: any) {
   console.warn(
     'useControlledValue is deprecated and will be removed in the next major version. Please use useControllableValue instead.',
   );
diff --git a/packages/hooks/src/useControllableValue/index.ts b/packages/hooks/src/useControllableValue/index.ts
index de6faebd..2a8e891e 100644
--- a/packages/hooks/src/useControllableValue/index.ts
+++ b/packages/hooks/src/useControllableValue/index.ts
@@ -12,7 +12,17 @@ export interface Props {
   [key: string]: any;
 }
 
-export default function useControllableValue<T>(props: Props = {}, options: Options<T> = {}) {
+interface StandardProps<T> {
+  value: T;
+  defaultValue?: T;
+  onChange: (val: T) => void;
+}
+function useControllableValue<T = any>(props: StandardProps<T>): [T, (val: T) => void];
+function useControllableValue<T = any>(
+  props?: Props,
+  options?: Options<T>,
+): [T, (v: T, ...args: any[]) => void];
+function useControllableValue<T = any>(props: Props = {}, options: Options<T> = {}) {
   const {
     defaultValue,
     defaultValuePropName = 'defaultValue',
@@ -22,7 +32,7 @@ export default function useControllableValue<T>(props: Props = {}, options: Opti
 
   const value = props[valuePropName] as T;
 
-  const [state, setState] = useState<T | undefined>(() => {
+  const [state, setState] = useState<T>(() => {
     if (valuePropName in props) {
       return value;
     }
@@ -40,7 +50,7 @@ export default function useControllableValue<T>(props: Props = {}, options: Opti
   }, [value, valuePropName]);
 
   const handleSetState = useCallback(
-    (v: T | undefined, ...args: any[]) => {
+    (v: T, ...args: any[]) => {
       if (!(valuePropName in props)) {
         setState(v);
       }
@@ -53,3 +63,5 @@ export default function useControllableValue<T>(props: Props = {}, options: Opti
 
   return [valuePropName in props ? value : state, handleSetState] as const;
 }
+
+export default useControllableValue;


Test patch:
diff --git a/packages/hooks/src/useControllableValue/__tests__/index.test.ts b/packages/hooks/src/useControllableValue/__tests__/index.test.ts
index 203f2f2c..4057daaa 100644
--- a/packages/hooks/src/useControllableValue/__tests__/index.test.ts
+++ b/packages/hooks/src/useControllableValue/__tests__/index.test.ts
@@ -25,12 +25,12 @@ describe('useControllableValue', () => {
   });
 
   it('onChange should work', () => {
-    let extraParam: string = ''
+    let extraParam: string = '';
     const props = {
       value: 2,
       onChange(v: any, extra) {
         this.value = v;
-        extraParam = extra
+        extraParam = extra;
       },
     };
     const hook = setUp(props);
@@ -69,4 +69,26 @@ describe('useControllableValue', () => {
     act(() => setValue(55));
     expect(result.current[0]).toEqual(55);
   });
+
+  it('type inference should work', async () => {
+    type Value = {
+      foo: number;
+    };
+    const props: {
+      value: Value;
+      defaultValue: Value;
+      onChange: (val: Value) => void;
+    } = {
+      value: {
+        foo: 123,
+      },
+      defaultValue: {
+        foo: 123,
+      },
+      onChange: () => {},
+    };
+    const hook = renderHook(() => useControllableValue(props));
+    const [v, setV] = hook.result.current;
+    expect(v.foo).toBe(123);
+  });
 });