React/Redux/Typescript - useDispatch with .then().catch()

Viewed 3910

I'm beginner with React/Redux. I want to authenticate a User and display a notification on my app when error occurs.

This is my login function:

  const [loading, setLoading] = useState(false);
  const dispatch = useDispatch();

  const handleSignIn = (values: SignInOpts, setSubmitting: any) => {
      setLoading(true);
      dispatch(authenticationActions.signInAndFetchUser(values))
        .then(res => {
          console.log("SignIn Success");
          setLoading(false);
        })
        .catch(err => {
          console.log("SignIn Failure");
          setLoading(false);
          showErrorNotification(
            "Error notification"
          );
        });
  };

My action:

export const signInAndFetchUser = (credentials: SignInOpts) => {
  return (dispatch, getState) => {
    return dispatch(signIn(credentials)).then(res => {
      const token = getState().authentication.token;
      return dispatch(getMe(token));
    });
  };
};

The error I have :

React/Redux error

How can I perform this ?

3 Answers

Most of your work should happen in the thunk (the action). dispatch does not return a promise. So you have to handle your promise inside your thunk and dispatch the corresponding action, it will then be send to the reducer. The new state will reflects the changes.

Here is a thunk which should give you an idea of how it works :

export const signInAndFetchUser = (credentials: SignInOpts) => {
  return (dispatch, getState) => {
    dispatch(action.startLoading);
    signIn(credentials)
      .then((res) => {
        // do something with res : maybe recover token, and store it to the store ?
        // if the token is in the store already why do you need to signIn ?
        dispatch(action.signedInSuccess(res.token));

        getMe(res.token)
          .then((user) => {
            dispatch(action.getMeSucceded(user));
          })
          .catch((err) => dispatch(action.getMeFailed(err)));
      })
      .catch((err) => dispatch(action.signInFailed(err)));
  };
};

Or using async/await :

export const signInAndFetchUser = (credentials: SignInOpts) => {
  return async (dispatch, getState) => {
    dispatch(action.startLoading);

    try {
      const res = await signIn(credentials);
      dispatch(action.signedInSuccess(res.token));

      try {
        const user = await getMe(res.token);
        dispatch(action.getMeSucceded(user));
      } catch (err) {
        dispatch(action.getMeFailed(err));
      }
    } catch {
      dispatch(action.signInFailed(err));
    }
  };
};

Generally for thunks, dispatch(myThunk()) will return whatever the thunk returns and can be a Promise, as also the case in your signInAndFetchUser method.

The problem is that the normal useDispatch hook typings do not know which middlewares you are using and thus have no overload for that behaviour.

That is why the Redux TypeScript Quickstart Tutorial recommends you define your own, correctly-typed hooks:

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

If you are using the official Redux Toolkit (especially with TypeScript, you definitely should as it cuts out almost all type annotations), you can just get

export type AppDispatch = typeof store.dispatch

If you are using old-style vanilla Redux, just use ThunkDispatch as AppDispatch.

dispatch do not return promise so you cannot pipe .then. From Action the pipeline flows to reducer, and reducer returns the state back to your component via useSelector for functional component and connect for class based component. So you need to hook those to receive login success object via state

Related