diff --git a/types/react/index.d.ts b/types/react/index.d.ts index a87874b9fd..929a698775 100644 --- a/types/react/index.d.ts +++ b/types/react/index.d.ts @@ -775,9 +775,13 @@ declare namespace React { type Dispatch = (value: A) => void; // Unlike redux, the actions _can_ be anything type Reducer = (prevState: S, action: A) => S; + // types used to try and prevent the compiler from reducing S + // to a supertype common with the second argument to useReducer() + type ReducerState> = R extends Reducer ? S : never; + type ReducerAction> = R extends Reducer ? A : never; // The identity check is done with the SameValue algorithm (Object.is), which is stricter than === // TODO (TypeScript 3.0): ReadonlyArray - type InputIdentityList = ReadonlyArray; + type DependencyList = ReadonlyArray; // NOTE: Currently, in alpha.0, the effect callbacks are actually allowed to return anything, // but functions are treated specially. The next version published with hooks will warn if you actually @@ -794,14 +798,14 @@ declare namespace React { * Accepts a context object (the value returned from `React.createContext`) and returns the current * context value, as given by the nearest context provider for the given context. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usecontext */ function useContext(context: Context/*, (not public API) observedBits?: number|boolean */): T; /** * Returns a stateful value, and a function to update it. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usestate */ function useState(initialState: S | (() => S)): [S, Dispatch>]; @@ -812,10 +816,54 @@ declare namespace React { * multiple sub-values. It also lets you optimize performance for components that trigger deep * updates because you can pass `dispatch` down instead of callbacks. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usereducer */ - function useReducer(reducer: Reducer, initialState: S, initialAction?: A | null): [S, Dispatch]; + // overload where "I" may be a subset of ReducerState; used to provide autocompletion. + // If "I" matches ReducerState exactly then the last overload will allow initializer to be ommitted. + // the last overload effectively behaves as if the identity function (x => x) is the initializer. + function useReducer, I>( + reducer: R, + initializerArg: I & ReducerState, + initializer: (arg: I & ReducerState) => ReducerState + ): [ReducerState, Dispatch>]; + /** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usereducer + */ + // overload for free "I"; all goes as long as initializer converts it into "ReducerState". + function useReducer, I>( + reducer: R, + initializerArg: I, + initializer: (arg: I) => ReducerState + ): [ReducerState, Dispatch>]; + /** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usereducer + */ + // I'm not sure if I keep this 2-ary or if I make it (2,3)-ary; it's currently (2,3)-ary. + // The Flow types do have an overload for 3-ary invocation with undefined initializer. + // NOTE: the documentation or any alphas aren't updated, this is current for master. + // NOTE 2: without the ReducerState indirection, TypeScript would reduce S to be the most common + // supertype between the reducer's return type and the initialState (or the initializer's return type), + // which would prevent autocompletion from ever working. + function useReducer>( + reducer: R, + initialState: ReducerState, + initializer?: undefined + ): [ReducerState, Dispatch>]; /** * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument * (`initialValue`). The returned object will persist for the full lifetime of the component. @@ -823,7 +871,7 @@ declare namespace React { * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable * value around similar to how you’d use instance fields in classes. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useref */ // TODO (TypeScript 3.0): @@ -839,7 +887,7 @@ declare namespace React { * Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type * of the generic argument. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useref */ // TODO (TypeScript 3.0): @@ -854,20 +902,20 @@ declare namespace React { * If you’re migrating code from a class component, `useLayoutEffect` fires in the same phase as * `componentDidMount` and `componentDidUpdate`. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#uselayouteffect */ - function useLayoutEffect(effect: EffectCallback, inputs?: InputIdentityList): void; + function useLayoutEffect(effect: EffectCallback, deps?: DependencyList): void; /** * Accepts a function that contains imperative, possibly effectful code. * * @param effect Imperative function that can return a cleanup function - * @param inputs If present, effect will only activate if the values in the list change. + * @param deps If present, effect will only activate if the values in the list change. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useeffect */ - function useEffect(effect: EffectCallback, inputs?: InputIdentityList): void; + function useEffect(effect: EffectCallback, deps?: DependencyList): void; // NOTE: this does not accept strings, but this will have to be fixed by removing strings from type Ref /** * `useImperativeHandle` customizes the instance value that is exposed to parent components when using @@ -875,23 +923,23 @@ declare namespace React { * * `useImperativeHandle` should be used with `React.forwardRef`. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useimperativehandle */ - function useImperativeHandle(ref: Ref|undefined, init: () => R, inputs?: InputIdentityList): void; + function useImperativeHandle(ref: Ref|undefined, init: () => R, deps?: DependencyList): void; // I made 'inputs' required here and in useMemo as there's no point to memoizing without the memoization key // useCallback(X) is identical to just using X, useMemo(() => Y) is identical to just using Y. /** * `useCallback` will return a memoized version of the callback that only changes if one of the `inputs` * has changed. * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usecallback */ // TODO (TypeScript 3.0): unknown> - function useCallback any>(callback: T, inputs: InputIdentityList): T; + function useCallback any>(callback: T, deps: DependencyList): T; /** - * `useMemo` will only recompute the memoized value when one of the `inputs` has changed. + * `useMemo` will only recompute the memoized value when one of the `deps` has changed. * * Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in * the second argument. @@ -905,10 +953,22 @@ declare namespace React { * } * ``` * - * @version experimental + * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usememo */ - function useMemo(factory: () => T, inputs: InputIdentityList): T; + function useMemo(factory: () => T, deps: DependencyList): T; + /** + * `useDebugValue` can be used to display a label for custom hooks in React DevTools. + * + * NOTE: We don’t recommend adding debug values to every custom hook. + * It’s most valuable for custom hooks that are part of shared libraries. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usedebugvalue + */ + // the name of the custom hook is itself derived from the function name at runtime: + // it's just the function name without the "use" prefix. + function useDebugValue(value: T, format?: (value: T) => any): void; // // Event System diff --git a/types/react/test/hooks.tsx b/types/react/test/hooks.tsx index 5cb0c1512c..12fcc28222 100644 --- a/types/react/test/hooks.tsx +++ b/types/react/test/hooks.tsx @@ -63,7 +63,13 @@ export function App() { const birthdayRef = React.useRef(null); React.useLayoutEffect(() => { - birthdayRef.current!.fancyClick(); + if (birthdayRef.current !== null) { + birthdayRef.current.fancyClick(); + } else { + // this looks redundant but it ensures the type actually has "null" in it instead of "never" + // $ExpectType null + birthdayRef.current; + } }); return <> @@ -85,13 +91,22 @@ const context = React.createContext({ test: true }); function useEveryHook(ref: React.Ref<{ id: number }>|undefined): () => boolean { const value: Context = React.useContext(context); const [, setState] = React.useState(() => 0); - const [reducerState, dispatch] = React.useReducer(reducer, initialState, { type: 'resetAge' }); + // Bonus typescript@next version + // const [reducerState, dispatch] = React.useReducer(reducer, true as const, arg => arg && initialState); + // Compile error in typescript@3.0 but not in typescript@3.1. + // const [reducerState, dispatch] = React.useReducer(reducer, true as true, arg => arg && initialState); + const [reducerState, dispatch] = React.useReducer(reducer, true as true, (arg: true): AppState => arg && initialState); + + // inline object, to (manually) check if autocomplete works + React.useReducer(reducer, { age: 42, name: 'The Answer' }); + // make sure this is not going to the |null overload // $ExpectType MutableRefObject const didLayout = React.useRef(false); const id = React.useMemo(() => Math.random(), []); React.useImperativeHandle(ref, () => ({ id }), [id]); + // was named like this in the first alpha, renamed before release // $ExpectError React.useImperativeMethods(ref, () => ({}), [id]); @@ -105,6 +120,9 @@ function useEveryHook(ref: React.Ref<{ id: number }>|undefined): () => boolean { setState(reducerState.age); }, []); + React.useDebugValue(id, value => value.toFixed()); + React.useDebugValue(id); + return React.useCallback(() => didLayout.current, []); } @@ -118,5 +136,6 @@ const UsesEveryHook = React.forwardRef( ); const everyHookRef = React.createRef<{ id: number }>(); ; + // TODO: "implicit any" in typescript@3.0 but not in typescript@3.1 // { ref && console.log(ref.id); }}/>;