23

All around it but not quite as I have enough of an idea of redux-thunk and of react-router but I am not getting this seemingly simple idea of:

Call a change in route programmatically via <Route/>'s history.push('/') after an action has finished dispatching to the store, this to happen when a button is pushed.

const LoggerRedux = ({stateProp, LogIn}) => {
    return(
      <div>
        <h2>Here is the button. Use this to login</h2>
        <Route render={({ history}) => (
          <button
            type='button'
            onClick={

            //Something that calls {LogIn}
            //and then the history.push('/')

            }
            >
            Log yo Arse Inn
          </button>
        )}>

        </Route>
      </div>
    )
}

const mapStateToProps = (state) => {
  return {
    stateProp : state
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    LogIn : () => dispatch({
      type : 'AUTHENTICATE'
    }),
    LogOut : (bool) => dispatch({
      type : 'LOGGED_OUT',
      bool
    })
  }
}
const LoginConn = connect( mapStateToProps, mapDispatchToProps)(LoggerRedux);

Layman explanations and examples of all things I mentioned/tagged would also be nice

KellysOnTop23
  • 1,325
  • 2
  • 15
  • 34

6 Answers6

52

Have your action creator return a promise. This way when you invoke it, you can use .then() and in your then handler you can push a new address to the history.

Thunks will return functions normally, but a promise differs in that you can act after completion.

Example:

static myActionCreator(somevar) {
  return dispatch => {
    return new Promise((resolve, reject) => {
      dispatch({
        type: "myaction",
        something: somevar
      });

      resolve()
    });
  }
}

So in this case, your thunk returns a promise. Then when you invoke like this:

this.props.myActionCreator(somevar)
.then(() => {
  this.props.history.push('/winning')
})

This thunk is just dispatching an action, but you could have some async call in there that has a callback, etc. and you resolve on completion.

Al.G.
  • 4,327
  • 6
  • 31
  • 56
Hashibuto
  • 748
  • 6
  • 10
  • I haven't seen a promise like this used in any kind of reac-redux documentation. Are you sure there isn't a more common way to fuse `react-router` and `redux` in this fashion? – KellysOnTop23 Nov 30 '17 at 05:41
  • @KellysOnTop23 Have a read of the redux-thunk documentation. They talk about using promises to this effect. react-redux deals in action objects, thunks are actions as functions (where you would find examples like this). – Hashibuto Nov 30 '17 at 20:16
  • any suggestions on how to do this using async-await? – PeterG Oct 31 '19 at 00:20
  • 1
    @PeterG Async and Await are just syntactic sugar around Promises, so the first code block there stays the same. The second (calling it) becomes ```await this.props.myActionCreator(somevar); this.props.history.push('/winning');``` – Justin Godesky Dec 08 '19 at 08:06
  • 2
    this doesn't work. the Promise has no idea what a dispatch is. why all the points? – johnrubythecat Sep 28 '21 at 01:05
  • 1
    I agree with @johnrubythecat. Even though dispatch is synchronous it will take around few milliseconds to update the redux state. So we are resolving the promise without knowing dispatch functions result. – Ryxle Mar 22 '22 at 05:16
4

I think what you are looking for is Redux Saga.

You can use the TakeEvery Effect, which will trigger the function you want every time a specific action is called.

Example :

import { takeEvery } from `redux-saga/effects`

function* fetchUser(action) {
  ...
}

function* watchFetchUser() {
  yield takeEvery('USER_REQUESTED', fetchUser)
}

Take a look at the doc and read this article

Sid Ali
  • 1,779
  • 4
  • 17
  • 38
1

I had similar confusion and ended up spending quite a time working on solving the problem through callbacks and javascript promises which is not required at all because of react-redux setup.

this.props.clearReducersState()
this.props.history.push('/dashboard')

I was trying to go to my dashboard after my clearReducerState() function is dispatched. This code works just fine.

You need to do nothing react-redux works on a synchronous way, so you can simply do this. You can use history.push in your action too for that,

import { withRouter } from 'react-router-dom';
this.props.clearReducersState(this.props.history)

export default connect(mapStateToProps, mapDispatchToProps)(withRouter(UpdateAid))

And in your action,

export const clearReducersState = (history) => dispatch => {
history.push('/dashboard')
}

However, if you are updating your state using reducers it is better to use history.push in your index file to avoid problems.

Use as per your requirement. Hope this helps.

sareek
  • 534
  • 7
  • 12
1

There is another way which I use, as suggested by the official Redux tutorials.

add an enum status to your state, and also a variable error = null

Then you can assign various types of status to them. In your case

status = "idle" | "pending" | "failed" | "loggedin" | "loggedout";

Then in your thunk,

if (login succeeds)
   status = 'loggedin';
else {
    status = 'failed';
    error = 'e.message if you are using try catch';
}

In your functional component,

const {status,error} = useSelector(state => ({state.status,state.error}));
const dispatch = useDispatch();
useEffect(()=>{
    if(status === "loggedin") 
         // don't forget to reset the status
         dispatch(setStatus('idle'));
         history.push('/dashboard');
    if(status === "failed")
        dispatch(setStatus('idle'));
        console.log(error);
},[status]);
        
Ashu Sahu
  • 417
  • 6
  • 7
0

I want to highlight this section here...

<Route render={({ history}) => (
  <button
    type='button'
    onClick={

    //Something that calls {LogIn}
    //and then the history.push('/')

    }
    >
    Log yo Arse Inn
  </button>
)}>

</Route>

You want to call Login and push the history to the root of the application. You have a couple options here:

  1. call the function history.push('/') within your Login function, this looks to be within your reducer AUTHENTICATE function.

  2. dispatch an action that calls history.push after successful authentication look at redux-promise or redux-saga, redux-thunk is a bit trickier to do, but its possible

I wouldn't try to do it any other way for the moment as you're dependant on login (which is called via redux, so I'd want to keep the logic in there, even though it's a client-side redirect).

Denis Tsoi
  • 9,428
  • 8
  • 37
  • 56
  • This is for learning before implementation but there will be axios involved later with those `.then()` promises so I could probably safely assume to execute that `history.push('/')` there as well – KellysOnTop23 Nov 30 '17 at 04:04
  • Also I can't because the `history` prop isn't being called in the Action process and I've seen that this isn't the best practice to pass `history`as a prop to functions – KellysOnTop23 Nov 30 '17 at 04:42
0

According to this stackoverflow answer: Is store.dispatch in Redux synchronous or asynchronous, redux dispatch functions are synchronous therefore, you can do this:

LogIn();
history.push("/");
JJJ
  • 3,314
  • 4
  • 29
  • 43
  • 3
    You are correct, they are synchronous if you aren't using thunks. Once you use thunks though, they cease to be synchronous. – Hashibuto Dec 08 '17 at 21:40