25

I develop a website with React/Redux and I use a thunk middleware to call my API. My problem concerns redirections after actions.

I really do not know how and where I can do the redirection: in my action, in the reducer, in my component, … ?

My action looks like this:

export function deleteItem(id) {
    return {
        [CALL_API]: {
            endpoint: `item/${id}`,
            method: 'DELETE',
            types: [DELETE_ITEM_REQUEST, DELETE_ITEM_SUCCESS, DELETE_ITEM_FAILURE]
        },
        id
    };
}

react-redux is already implemented on my website and I know that I can do as below, but I do not want to redirect the use if the request failed:

router.push('/items');

Thanks!

Kostas Minaidis
  • 4,681
  • 3
  • 17
  • 25
vermotr
  • 663
  • 2
  • 10
  • 22

8 Answers8

20

Definitely do not redirect from your reducers since they should be side effect free. It looks like you're using api-redux-middleware, which I believe does not have a success/failure/completion callback, which I think would be a pretty useful feature for the library.

In this question from the middleware's repo, the repo owner suggests something like this:

// Assuming you are using react-router version < 4.0
import { browserHistory } from 'react-router';

export function deleteItem(id) {
  return {
    [CALL_API]: {
      endpoint: `item/${id}`,
      method: 'DELETE',
      types: [
        DELETE_ITEM_REQUEST, 
        {
          type: DELETE_ITEM_SUCCESS,
          payload: (action, state, res) => {
            return res.json().then(json => {
              browserHistory.push('/your-route');
              return json;
            });
          },
        },
        DELETE_ITEM_FAILURE
      ]
    },
    id
  }
};

I personally prefer to have a flag in my connected component's props that if true, would route to the page that I want. I would set up the componentWillReceiveProps like so:

componentWillReceiveProps(nextProps) {
  if (nextProps.foo.isDeleted) {
    this.props.router.push('/your-route');
  }
}
Yo Wakita
  • 5,322
  • 3
  • 24
  • 36
  • 7
    How you then reset back this flag `isDeleted`? If someone comes back to this component it's a big chance he will be automatically redirected without doing anything. – Tomasz Mularczyk Jan 06 '18 at 10:48
  • 4
    @Tomasz I had the same concern. Wouldn't this get cumbersome if the new component would have to reset all state variables that are used in this redirection process Seems unwieldy. – Ryan Jan 22 '18 at 01:00
  • 1
    Jesus Christ - this answer is so amazing! I've spent so much time on such a simple issue. This solution is elegant and simple. Thank you so much @Yo Wakita! – Arkadiusz Kałkus Oct 03 '18 at 19:58
13

The simplest solution

You can use react-router-dom version *5+ it is actually built on top of react-router core.

Usage:

You need to import useHistory hook from react-router-dom and directly pass it in your actions creator function call.

Import and Creating object

import { useHistory } from "react-router-dom";

const history = useHistory();

Action Call in Component:

dispatch(actionName(data, history));

Action Creator Now, you can access history object as a function argument in action creator.

function actionName(data, history) {}
Muhammad Usman
  • 2,419
  • 1
  • 11
  • 18
8

Usually the better practice is to redirect in the component like this:

render(){
   if(requestFullfilled){
       router.push('/item')
   }
   else{
       return(
          <MyComponent />
       )
   }
}
Prakash Sharma
  • 15,542
  • 6
  • 30
  • 37
2

In the Redux scope must be used react-redux-router push action, instead of browserHistory.push

import { push } from 'react-router-redux'

store.dispatch(push('/your-route'))
Igor S
  • 951
  • 7
  • 19
0

I would love not to redirect but just change the state. You may just omit the result of deleted item id:

// dispatch an action after item is deleted
dispatch({ type: ITEM_DELETED, payload: id })

// reducer
case ITEM_DELETED:
  return { items: state.items.filter((_, i) => i !== action.payload) }
Bhojendra Rauniyar
  • 83,432
  • 35
  • 168
  • 231
0

Another way is to create a redirect function that takes in your redirect Url:

const redirect = redirectUrl => {
  window.location = redirectUrl;
};

Then import it into your action after dispatch and return it;

return redirect('/the-url-you-want-to-redirect-to');
Anayo Oleru
  • 468
  • 5
  • 8
  • this is generally bad practice, window.location will make the browser reload the whole app, unless it's intentional for some reason, it's usually not what we want... – orszaczky Apr 10 '21 at 13:37
0
import { useHistory } from "react-router-dom";
import {useEffect} from 'react';

const history = useHistory();

useEffect(()=>{
    // change if the user has successfully logged in and redirect to home 
    //screen
    if(state.isLoggedIn){
      history.push('/home');
    }
// add this in order to listen to state/store changes in the UI
}, [state.isLoggedIn])
Kasujja Muhammed
  • 422
  • 6
  • 11
  • Code dumps without any explanation are rarely helpful. Stack Overflow is about learning, not providing snippets to blindly copy and paste. Please [edit] your question and explain how it answers the specific question being asked. See [answer]. This is especially important when answering old questions (this one is 4 years old) with existing answers. How does this answer improve upon the other 6? – ChrisGPT was on strike Apr 11 '21 at 18:07
0

A simple solution would be to check for a state change in componentDidUpdate. Once your state has updated as a result of a successful redux action, you can compare the new props to the old and redirect if needed.

For example, you dispatch an action changing the value of itemOfInterest in your redux state. Your component receives the new state in its props, and you compare the new value to the old. If they are not equal, push the new route.

componentDidUpdate(prevProps: ComponentProps) {
  if (this.props.itemOfInterest !== prevProps.itemOfInterest) {
    this.props.history.push('/');
  }
}

In your case of deleting an item, if the items are stored in an array, you can compare the new array to the old.