22

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!

vermotr
  • 573
  • 1
  • 7
  • 22

7 Answers7

19

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
  • 4,514
  • 3
  • 18
  • 34
  • 6
    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! – Landeeyo Oct 03 '18 at 19:58
7

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

render(){
   if(requestFullfilled){
       router.push('/item')
   }
   else{
       return(
          <MyComponent />
       )
   }
}
Prakash Sharma
  • 11,610
  • 3
  • 24
  • 33
6

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
  • 199
  • 1
  • 11
  • This works for me. Can you just use the history inside the action.js instead of passing in the action function? Thanks anyway. – Roel John Mar 24 '21 at 00:25
1

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 Stetsiura
  • 775
  • 6
  • 14
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
  • 73,156
  • 29
  • 131
  • 187
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
  • 175
  • 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])
  • 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? – Chris Apr 11 '21 at 18:07