Person
Person

Reputation: 2269

How to redirect from component after action was called successfully in React?

I have a call to an API inside an action in redux.

export const registerUser = registeredUserData => async dispatch => {
  let messages;
  try {
    const response = await axios.post('/users/register', registeredUserData);
    messages = response.data
  } catch (error) {
    if (error.response) {
      messages = error.response.data
    }
  }

  dispatch({
    type: REGISTER_USER,
    messages,
  });
};

This action is called when a form is sumbitted.

const onRegisterUser = (e) => {
    e.preventDefault();
    registerUser(registeredUserData);
  };

When, if a call was successfull I want to redirect to another page. The problem I'm facing is that I don't know how to implement history.push() in this case.

If I put it inside method of my component right after registerUser(registeredUserData); then it gets called right away no matter the response of the call. And I'm not sure if it is a good idea to redirect from the action itself.

All the help will be much appreciated.

Upvotes: 0

Views: 767

Answers (3)

ambrons
ambrons

Reputation: 136

In your example your action registerUser is a promise since it's an async function. So you could rewrite your onRegisterUser to look like this:

const onRegisterUser = (e) => {
    e.preventDefault();
    registerUser(registeredUserData)
        .then(() => /* success */)
        .catch(error => /* handle my failure */)
};

That being said you might want to consider creating SUCCESS and FAILURE actions for your network call. This allows you to potentially update the state of redux based on your register user api call.

You could modify your thunk to look like this:

export const registerUser = registeredUserData => async dispatch => {
  try {
    const response = await axios.post('/users/register', registeredUserData);
    dispatch({
      type: REGISTER_USER_SUCCESS,
      messages: response.data,
    });
  } catch (error) {
    if (error.response) {
      dispatch({
        type: REGISTER_USER_FAILURE,
        messages: error.response.data,
      });
    }
  }
};

You can then use one of React lifecycle methods to check for the state in redux change. Assuming the snippet is using react-redux and connect.

You might also want to consider looking into action creators.

An alternative to using React lifecycle methods is to use something like redux-saga which can signal on the SUCCESS and FAILURE actions and do the history.push on your behalf.

You might also want to look at react router if you haven't done so yet. It provides ways to access history and manage routing.

The point of async / await is to not have to use a nested promise chain in the case of your example.

Your try / catch block is equivalent to your then / catch. So if you want to use the above and have it catch when the response is a 400 you will either need to remove the try catch and handle the error in onRegisterUser, not recommended, or you will need to re-throw so that the catch is called when you call registerUser.

Here's an example on registerUser that should return a catch when failed response.

export const registerUser = registeredUserData => async dispatch => {
  try {
    const response = await axios.post('/users/register', registeredUserData);
    await dispatch({
      type: REGISTER_USER,
      messages: response.data,
    });
  } catch (error) {
    if (error.response) {
      await dispatch({
        type: REGISTER_USER,
        messages: error.response.data,
        isError: true,
      });
      throw new Error(error.response.data);
    }
  }
};

You might want to replace throw new Error(error.response.data) with something more specific by decorating the error object.

Upvotes: 2

Sultan H.
Sultan H.

Reputation: 2938

Hi Allan,

You'll basically have this.props.history.push available from your Router which is passing it to all the Route components children in your app.

You can confirm this via console.log('__props__', this.props) in your render method for that component.

In order to implement this, I would suggest sending it as a callback to your action registerUser, in order to do this:

  • Add the cb to your action:
export const registerUser = registeredUserData => async dispatch => {
  let messages;
  try {
    const response = await axios.post('/users/register', registeredUserData);
    messages = response.data
  } catch (error) {
    if (error.response) {
      messages = error.response.data
    }
  }

  dispatch({
    type: REGISTER_USER,
    messages,
  });

  // maybe you want a conditional redirect?
  if(/*somecondition to check data or error?*/){
    cb && cb(); //if callback exists, invoke it
  }
};
  • And for: onRegisterUser:
const onRegisterUser = (e) => {
  e.preventDefault();
  registerUser(registeredUserData, () => {
    this.props.history.push(//route);
  });
};

Hope that works, if it doesn't please describe the behavior.

Upvotes: 0

Rohit Kashyap
Rohit Kashyap

Reputation: 1592

You are almost there. In your component, pass this.props.history as a parameter to the redux action. And from there, after the action is dispatched you can redirect to some other page.

P.S: It's not a bad idea to redirect from the action itself.

Inside your component:

const onRegisterUser = (e) => {
    e.preventDefault();
    registerUser(registeredUserData, this.props.history);
  };

Inside your action:

export const registerUser = (registeredUserData, history) => async dispatch => {
  let messages;
  try {
    const response = await axios.post('/users/register', registeredUserData);
    messages = response.data
  } catch (error) {
    if (error.response) {
      messages = error.response.data
    }
  }

  dispatch({
    type: REGISTER_USER,
    messages,
  });

    history.push('/redirect-route);
};

Upvotes: 0

Related Questions