如何使用 redux-thunk 调度操作让 connected-react-router 重定向?

Posted

技术标签:

【中文标题】如何使用 redux-thunk 调度操作让 connected-react-router 重定向?【英文标题】:How do I get connected-react-router to redirect using a redux-thunk dispatch action? 【发布时间】:2018-11-27 14:21:41 【问题描述】:

我正在开展一个新项目。我正在使用带有 redux 和 thunk 的 create-react-app。 api 服务器是一个单独的项目,数据库使用 node/express 和 mongo/mongoose。我试图在创建或删除项目操作后重定向到项目列表视图。如果重定向在组件本身中,则重定向发生在操作完成之前,并且列表的重新获取发生在创建或删除发生之前。因此,我尝试使用 thunk 在动作的 .then() 部分中调度重定向动作。重定向成功地改变了浏览器中的 url,但没有触发重新渲染。如何从创建操作或删除操作获得重定向以触发库存列表组件的重新呈现?

完整代码在:https://github.com/jhlindell/BarCode-app/tree/stockitem 服务器在:https://github.com/jhlindell/BarCode-server/tree/jon

我的 index.js:

import 'bootstrap/dist/css/bootstrap.css';
import './index.css';
import App from './App';
import  Provider  from 'react-redux';
import React from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import  ConnectedRouter  from 'connected-react-router'

import  createStore, applyMiddleware, compose  from 'redux';
import  composeWithDevTools  from 'redux-devtools-extension';
import  createBrowserHistory  from 'history';
// import  browserHistory  from 'react-router'
import reducers from './reducers';
import thunk from 'redux-thunk';
import  connectRouter, routerMiddleware  from 'connected-react-router'
import  logger  from 'redux-logger';

const history = createBrowserHistory();
const reactRouterMiddleware = routerMiddleware(history); 

const middleWares = [
  thunk,
  logger,
  reactRouterMiddleware
]

const store = createStore(
  connectRouter(history)(reducers), 
  composeWithDevTools(applyMiddleware(...middleWares)));

ReactDOM.render(
  <Provider store = store>
    <ConnectedRouter history=history>
      <App history=history/>
    </ConnectedRouter>
  </Provider>
, document.getElementById('root'));
registerServiceWorker();

我的 app.js 文件:

import './App.css';
import  BrowserRouter as Router, Route, Switch  from 'react-router-dom';
import Footer from './components/Nav/Footer';
import NavBar from './components/Nav/NavBar';
import React,  Component  from 'react';
import StockItemCreate from './components/StockItems/StockItemCreate';
import StockItemDetail from './components/StockItems/StockItemDetail';
import StockItemList from './components/StockItems/StockItemList';
import HomePage from './components/HomePage';


class App extends Component 
  com

  render() 
    const flexCol = 
      display: 'flex',
      flexDirection: 'column',
    ;

    const flex0 = 
      flex: 0
    ;

    const flex1 = 
      display: 'flex',
      flex: '1 1 100%',
    ;
    return (
      <Router>
        <div className="App" style=flexCol>
          <div style=flex0>
            <NavBar />
          </div>
          <div style=flex1 id="mainBlock">
            <Switch>
              <Route exact path='/' component=HomePage />
              <Route exact path='/stockitems/create' component=StockItemCreate />
              <Route exact path='/stockitems' component=StockItemList />
              <Route path='/stockitems/:id' component=StockItemDetail />
            </Switch>
          </div>
          <div style=flex0>
            <Footer />
          </div>
        </div>
      </Router>
    );
  


export default App;

列表组件:

import  bindActionCreators  from 'redux';
import  connect  from 'react-redux';
import React, Component from 'react';
import  getStockItemList, clearStockItemList  from '../../actions';

const listStyle = 
    display: 'flex',
    margin: 'auto'


class StockItemList extends Component 
  componentDidMount()
    this.props.getStockItemList();
  

  componentWillUnmount()
    this.props.clearStockItemList();
  

  render()
    return (
      <div style=listStyle>
        this.props.stockItemList ? <ul className="list-group">
          this.props.stockItemList.map((item) => 
            return <li 
            className="list-group-item" 
            key=item.name
            onClick=()=> this.props.history.push(`/stockitems/$item._id`)
            >item.name</li>
          )
        </ul> : <span>loading...</span>
      </div>
    );
  


function mapStateToProps(state)
  return  stockItemList: state.stockItemList 


function mapDispatchToProps(dispatch)
  return bindActionCreators( getStockItemList, clearStockItemList , dispatch)


export default connect(mapStateToProps, mapDispatchToProps)(StockItemList);

创建组件:

import  bindActionCreators  from 'redux';
import  connect  from 'react-redux';
import React, Component from 'react';
import  createStockItem  from '../../actions'

const cardStyle = 
  display: 'flex',
  margin: 'auto',
;

const formStyle = 
  display: 'flex',
  flexDirection: 'column',
  width: '80%',
  margin: 'auto'
;

class StockItemCreate extends Component
  constructor(props)
    super(props);
    this.state = 
      name: '',
      description: ''
    
  
  handleFormSubmit = (event) => 
    event.preventDefault();
    this.props.createStockItem(this.state);
    this.clearForm();
    //this.props.history.push('/stockitems');
  

  handleInputChange = (event) => 
    const target = event.target;
    const value = target.value;
    const name = target.name;
    this.setState([name]: value);
  

  clearForm = () => 
    this.setState( name: '', description: '');
  

  render()
    return(

        <form className="card" onSubmit=this.handleFormSubmit style=cardStyle>
            <div className="card-header">
              <h3>Add new ingredient</h3>
            </div>
            <div className="card-block mt-2">
              <div style=formStyle> 
                <label>Name</label>
                <input name="name" type="text"
                  onChange=(e) => this.handleInputChange(e)
                  placeholder="Name"
                  value=this.state.name/>             
              </div>
              <div className="mt-2" style=formStyle> 
                <label>Description</label>
                <input name="description" type="text"
                  onChange=(e) => this.handleInputChange(e)
                  placeholder="Description"
                  value=this.state.description/>             
              </div>
            </div>
            <div className="btn-group mb-2 mt-2" style=padding: '0', margin: 'auto'>
              <button className="btn btn-primary" type="submit">
                Submit
              </button>
              <button className="btn btn-secondary" type="button" onClick=()=>this.clearForm()>
                Cancel
              </button>
            </div>
        </form>

    )
  


function mapDispatchToProps(dispatch)
  return bindActionCreators( createStockItem , dispatch);


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

最后是调用重定向的动作文件:

import axios from 'axios';
import  push, replace  from 'connected-react-router'


const URL = 'http://localhost:8000';

export function getStockItemList()
  return function(dispatch)
    axios.get(`$URL/api/stock_items/`)
      .then((response) => 
        dispatch( type: 'STOCK_ITEM_LIST', payload: response.data );
      )
      .catch((error) => 
        console.log('error getting stock items');
      );
  


export function clearStockItemList()
  return  type: 'CLEAR_STOCK_ITEM_LIST' ;


export function getStockItemById(id)
  return function(dispatch)
    axios.get(`$URL/api/stock_items/$id`)
      .then((response) => 
        dispatch( type: 'SINGLE_STOCK_ITEM', payload: response.data );
      )
      .catch((error) => 
        console.log('error getting stock item by id');
        dispatch(push('/stockitems'));
      );
  


export function clearSingleStockItem()
  return  type: 'CLEAR_SINGLE_STOCK_ITEM' ;


export function createStockItem(item)
  return function(dispatch)
    axios.post(`$URL/api/stock_items/`, item)
      .then((response)=> 
        console.log("response", response);
        dispatch(push('/stockitems'));
      )
      .catch((error) => 
        //create error container to post error to
        console.log('error creating stock item', error);
      );
  


export function deleteStockItem(id)
  return function(dispatch)
    axios.delete(`$URL/api/stock_items/$id`)
      .then((response)=> 
        console.log("delete response: ", response);
        dispatch(push('/stockitems'));
      )
      .catch((error) => 
        //create error container to post error to
        console.log('error deleting stock item', error);
      );
  

【问题讨论】:

【参考方案1】:

考虑使用connected-react-router 中的push 作为“动作创建者”而不是this.props.history.push

【讨论】:

【参考方案2】:

根据this SO answer:

从此改变:

    <Switch>
      <Route path="/">
        <Welcome />
      </Route>
      <Redirect to="/" />
    </Switch>

到这里:

    <Switch>
      <>
        <Route path="/">
          <Welcome />
        </Route>
        <Redirect to="/" />
      </>
    </Switch>

...为我工作。

我试图为任何未明确指定的路径实现基本的后备重定向,因此像 http://localhost:3000/askfjasdf 这样的随机路径将重定向到 http://localhost:3000。出于某种原因,将片段添加为 &lt;Switch&gt; 的***子级就可以了。

【讨论】:

这不是正确的做法。你应该使用exact - ***.com/questions/49162311/…

以上是关于如何使用 redux-thunk 调度操作让 connected-react-router 重定向?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 redux-thunk 和 TypeScript 调度 ThunkAction

使用 Redux-Thunk / Axios 从 onUploadProgress 事件调度操作

jest redux-thunk 测试是不是调度了相同模块的操作

在React / Redux中,如果函数组件正在使用redux-thunk调度函数,则如何setIsLoading()?

使用 ConnectedProps 和 redux-thunk 获取正确的调度类型

使用 axios 和 redux-thunk 以 redux-form 发布请求