reactjstypescriptredux-thunk

TypeScript: Type 'T' does not satisfy the constraint '(...args: any) => any'


I have the following redux-thunk action creator:

function updateInitiator(form_request_id, { to_recipient }) {
  return (dispatch) => {
    const url = `/some/url`;
    const data = { to_recipient };

    return fetch(url, { method: 'PUT', body: JSON.stringify(data) }).then(() => {
      dispatch(fetchResponses());
    });
  };
}

Then I declare the type of the function:

type UpdateInitiator = typeof updateInitiator;

I'm trying to derive the type of a bound thunk action. In short, when the action creator is "bound" in react-redux, it automatically calls the function returned with dispatch and then returns the return result of that internal function. I'm trying to declare a type for this behavior. It works if I do it without generics:

type BoundUpdateInitiator = (...args: Parameters<UpdateInitiator>) => ReturnType<ReturnType<UpdateInitiator>>;

But when I try to declare a generic type for any bound function, I'm having some trouble:

type BoundThunk<T> = (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
type BoundUpdateInitiator = BoundThunk<UpdateInitiator>;

This gives me the error:

error TS2344: Type 'T' does not satisfy the constraint '(...args: any) => any'.

236 type BoundThunk<T> = (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
                                              ~

error TS2344: Type 'ReturnType<T>' does not satisfy the constraint '(...args: any) => any'.
  Type 'unknown' is not assignable to type '(...args: any) => any'.
    Type '{}' provides no match for the signature '(...args: any): any'.

236 type BoundThunk<T> = (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
                                                                ~~~~~~~~~~~~~

error TS2344: Type 'T' does not satisfy the constraint '(...args: any) => any'.

236 type BoundThunk<T> = (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
                                                                           ~

I can vaguely understand that T might be things other than a function, and similarly ReturnType<T> might also not be a function, and this generic type maybe doesn't account for those cases. I'm having trouble understanding how I can account for them, however. Ideally by not allowing them. Any suggestions?


Solution

  • In order to accomplish this we would use a generic type constraint.

    This is in fact how the type parameters of the language provided utility types Parameters<T> and ReturnType<T> are themselves specified and thus produce the error you receive.

    In order to instantiate a generic type, say ReturnType<T>, with our type parameter, our type parameter must be constrained at least as restrictively as the type parameter named T declared by ReturnType<T>.

    By looking at the declaration of ReturnType<T>, as provided by the containing lib file, we can determine the minimum constraint we must apply to our own type and also find an example of the proper syntax for expressing said constraint.

    type ReturnType<T extends (...args: any[]) => any> =
        // details
    

    Don't worry about the implementation (after the =) as that is a broader topic. In this situation, we will focus on the constraints of T specificied using the extends keyword at the declaration of T.

    Therefore, in order to pass the T declared by BoundThunk<T> to ReturnType<T> we must constrain it to meet the requirements above (note that Parameters<T> has identical constraints).

    type BoundThunk<T extends (...args: any[]) => any> =
        (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
    

    However, our requirements for T are actually more restrictive because we apply ReturnType<T> twice, ReturnType<ReturnType<T>>, implying that T is a higher order function, in this case a function that returns a function.

    We will therefore refine our constraint accordingly

    type BoundThunk<T extends (...args: any[]) => (...args: any[]) => any> =
        (...args: Parameters<T>) => ReturnType<ReturnType<T>>;
    

    Via the above adjustment, which further constrains T to be some function which returns another function, we allow ourselves to write ReturnType<ReturnType<T>>.