Search code examples
javascriptreactjsreduxactionredux-toolkit

How do you navigate once your action has completed with redux-toolkit and createAsyncThunk()?


Defined action signInUser:

export const signInUser = createAsyncThunk(
  "auth/loginUser",
  async (user, thunkAPI) => {
    const { username, password } = user;
    try {
      const res = await axios.post("/api/auth/login", { username, password });
      thunkAPI.dispatch(loginUser(res.data));
    } catch (err) {
      console.log(err.message);
      thunkAPI.dispatch(setErrors(err.response.data));
    }
  }
);

On form submission, handleSubmit() dispatches signInUser() action.

Component:

const Login = () => {
  const dispatch = useDispatch();
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    const { username, password } = formData;
    try {
      await dispatch(signInUser({ username, password }));
      // DON'T WANT BELOW CODE TO RUN IF signInUser() FAILS (BUT IT DOES)
      navigate("/posts");
    } catch (err) {
      console.log(err.message);
      // NEED THIS CATCH TO RUN IF IT FAILS
    }
  };

Navigation is not possible inside of the signInUser() action - as far as I know.

So I'm trying to navigate from within the handleSubmit() of the Login component. But if the signInUser() action trips its catch (from a failed login) then I don't want to navigate("/posts") in the handleSubmit(). I think I need it to trip to the catch in the handleSubmit() and not execute navigate("/posts").

Or handle it some other way?


Solution

  • createAsyncThunk Thunks always return a resolved Promise with either the fulfilled or rejected action object inside. See the Handling Thunk Results for the full details, but the basic gist is that the returned thunks have an unwrap property that extracts the fulfilled payload or throw the error object or payload if returned by rejectWithValue. See Unwrapping Result Actions.

    Unwrap the returned Promise.

    const handleSubmit = async (e) => {
      e.preventDefault();
      const { username, password } = formData;
      try {
        await dispatch(signInUser({ username, password })).unwrap(); // <-- unwrap Promise
        navigate("/posts");
      } catch (err) {
        console.log(err);
      }
    };
    

    If the UI needs either the value or error from the Thunk then these should also be returned.

    export const signInUser = createAsyncThunk(
      "auth/loginUser",
      async (user, thunkAPI) => {
        const { username, password } = user;
        try {
          const { data } = await axios.post("/api/auth/login", { username, password });
          thunkAPI.dispatch(loginUser(data));
          return data; // <-- return POST response data
        } catch (err) {
          thunkAPI.dispatch(setErrors(err.response.data));
          return thunkAPI.rejectWithValue(err.response.data); // <-- return error payload
        }
      }
    );