最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Redux & React Router: Combing dispatch and navigation (history.push) - Stack Overflow

programmeradmin5浏览0评论

I'm a bit clueless as to how to use React Router's history.push('/route') with Redux.

In other words, if you connect a ponent with Redux's mapDispatchToProps, how do you put history.push('/route') that is supposed to be fired after dispatching a certain action?

To clarify the issue, here is a snippet of my experimentation...

class PostContainer extends React.Component {

    handleSubmit(data) {
        return this.props.addPost(data);
    }

    render() {
        return (<Post onSubmit={data=> this.handleSubmit(data)}/>);
    }
}

const mapDispatchToProps = (dispatch) => {
    return {    
        addPost: (data) => {
            dispatch(addPost(data)).data.then((result) => {
                dispatch(addPostFulfilled(result.data));

                //How do you call the following function?
                this.props.history.push('/posts')
            }).catch((error) => {
                dispatch(addPostRejected());
            });
        },      
    }
}

export default connect(null, mapDispatchToProps)(PostContainer);

As you can see, as soon as a post is added into Redux's state through addPostFulfilled, the page needs to move to /posts via history.push.

According to Redux's doc, containers are made for updating states (as well as fetching data). So, dumb ponents are not supposed to put anything like history.push.

What would be a better approach than the code above?

Any advice will be appreciated.

I'm a bit clueless as to how to use React Router's history.push('/route') with Redux.

In other words, if you connect a ponent with Redux's mapDispatchToProps, how do you put history.push('/route') that is supposed to be fired after dispatching a certain action?

To clarify the issue, here is a snippet of my experimentation...

class PostContainer extends React.Component {

    handleSubmit(data) {
        return this.props.addPost(data);
    }

    render() {
        return (<Post onSubmit={data=> this.handleSubmit(data)}/>);
    }
}

const mapDispatchToProps = (dispatch) => {
    return {    
        addPost: (data) => {
            dispatch(addPost(data)).data.then((result) => {
                dispatch(addPostFulfilled(result.data));

                //How do you call the following function?
                this.props.history.push('/posts')
            }).catch((error) => {
                dispatch(addPostRejected());
            });
        },      
    }
}

export default connect(null, mapDispatchToProps)(PostContainer);

As you can see, as soon as a post is added into Redux's state through addPostFulfilled, the page needs to move to /posts via history.push.

According to Redux's doc, containers are made for updating states (as well as fetching data). So, dumb ponents are not supposed to put anything like history.push.

What would be a better approach than the code above?

Any advice will be appreciated.

Share Improve this question edited Dec 24, 2017 at 6:58 Hiroki asked Dec 24, 2017 at 3:31 HirokiHiroki 4,17314 gold badges58 silver badges96 bronze badges
Add a ment  | 

4 Answers 4

Reset to default 7

You can either user react-router-redux (require an initial set up) and dispatch your action from action creators

import { push } from 'react-router-redux'
class PostContainer extends React.Component {

  handleSubmit(data) {
    return this.props.addPost(data);
  }

  render() {
    return (
      <div>
        <Post onSubmit={data=> this.handleSubmit(data)}/>
      </div>
    );
  }
}

const mapDispatchToProps = (dispatch) => {
  return {    
    addPost: (data) => {
      dispatch(addPost(data)).data.then((result) => {
        dispatch(addPostFulfilled(result.data));

        //How do you call the following function?
        dispatch(push('/posts'));
      }).catch((error) => {
        dispatch(addPostRejected());
      });
    },      
  }
}
export default connect(null, mapDispatchToProps)(PostContainer);

or just pass push as a "done callback".

class PostContainer extends React.Component {

  handleSubmit(data, done) {
    return this.props.addPost(data, done);
  }

  render() {
    const { push } = this.props.history;
    return (
      <div>
        <Post onSubmit={data=> this.handleSubmit(data, push)}/>
      </div>
    );
  }
}

const mapDispatchToProps = (dispatch) => {
  return {    
    addPost: (data, done) => {
      dispatch(addPost(data)).data.then((result) => {
        dispatch(addPostFulfilled(result.data));

        //How do you call the following function?
        done('/posts')
      }).catch((error) => {
        dispatch(addPostRejected());
      });
    },      
  }
}

export default connect(null, mapDispatchToProps)(PostContainer);

A cleaner approach might be to use React Router's <Redirect>:

class PostContainer extends React.Component {
  render() {
    if (userJustAddedAPostRightNow === true) {
      return <Redirect to='/posts' />
    }
    return (
      <div>
        <Post onSubmit={addPost}/>
      </div>
    );
  }
}
export default connect(null, { addPost })(PostContainer);

addPost would trigger a change of state if successful. It should changeuserJustAddedAPostRightNow to true, so that you'll be redirected to the posts' page.

To avoid all those nested callbacks you can follow Nir Kaufman's approach and handle logic in middlewares:

//src/middlewares/post.js
export const newPost = ({dispatch}) => next => action => {
  next(action);

  if (action.type === ADD_POST) {
    dispatch(apiRequest('POST', action.URL, null, ADD_POST_SUCCESS, ADD_POST_ERROR));
    dispatch(showSpinner());
  }
};

Then, on success, ADD_POST_SUCCESS action would change, via your reducer, the value of userJustAddedAPostRightNow.

IMHO , you can do a history.push even from your dumb ponents,i.e

const mapDispatchToProps = (dispatch) => {
    return {    
        addPost: (data) => {
            dispatch(addPost(data)).data.then((result) => {
                dispatch(addPostFulfilled(result.data));
                <!-- removed from here -->
            }).catch((error) => {
                dispatch(addPostRejected());
            });
        },      
    }
}

Once your addPostFulFilled data is done you have the option of doing your this.history.push('/route') in your ponent itself. You can do it any of the lifefcycle methods which is called when the props update. Perhaps, add it to ponentWillReceiveProps.

class PostContainer extends React.Component {
  ponentWillReceiveProps(nextProps){
  /*Added here*/
  if(nextProps.someFlag){ this.props.history.push('/someRoute');
}}

Note that the above works only if

  1. You are Ok to not track location changes via the store.(Read More)
  2. You are using react-router-4 in which case you will be wrapping your connect with withRouter which will enable the history object to be present as a prop in the PostContainer.
  3. Care should be taken to prevent unnecessary rerenders and write necessary logic in shouldComponentUpdate.

If the above sounds like a whole lot of gibberish , you can always go with react-router-redux's push API which enable pushing new entries in browser history. You can import that in any place of your choice and just push a new route.

Details can be seen here in the react-router-redux github pages and to quote the docs

Both push and replace take in a location descriptor, which can be an object describing the URL or a plain string URL. These action creators are also available in one single object as routerActions, which can be used as a convenience when using Redux's bindActionCreators().

So use the history.push() inside your mapDispatchToProps directly, or pass them as an prop to be used inside your PostContainer react ponent.


In my case after history.push() doesn't work directly,

So I found an alternative that, we should reload windows by window.location.reload();

It work for me!

发布评论

评论列表(0)

  1. 暂无评论