【问题标题】:React Hooks with React Router v4 - how do I redirect to another route?React Hooks with React Router v4 - 我如何重定向到另一个路由?
【发布时间】:2019-07-01 22:43:51
【问题描述】:

我有一个简单的反应钩子应用程序 - 待办事项列表 - 带有反应路由器 v4

在待办事项列表中,当点击待办事项时,我需要:

  1. 在上下文中调度当前的待办事项
  2. 重定向到另一条路线(从 /todos 到 /todos/:id)

在之前的基于 React 类的实现中,我可以使用 this.context.history.push 重定向到另一个路由。

我将如何使用 React Hooks 结合 React Router v4 来处理这个问题(在下面的代码中,请参阅我在函数 editRow() 中的注释)?

代码如下:

=====index.js=====

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter} from "react-router-dom"

import App from './App';

ReactDOM.render(
    <BrowserRouter>
        <App />
    </BrowserRouter>, document.getElementById('root'));

=====main.js=====

import React from 'react'
import { Switch, Route } from 'react-router-dom'
import TodosList from './todoslist'
import TodosEdit from './todosedit'

const Main = () => (
  <main>
    <Switch>
      <Route exact path="/todos" component={TodosList}/>
      <Route exact path="/todos/:id" component={TodosEdit} />
    </Switch>
  </main>
)

export default Main

=====app.js=====

import React, {useContext, useReducer} from 'react';
import Main from './main'
import TodosContext from './context'
import todosReducer from './reducer'

const App = () => {
  const initialState = useContext(TodosContext);
  const [state, dispatch] = useReducer(todosReducer, initialState);
  return (
    <div>
      <TodosContext.Provider value={{state, dispatch}}>
        <Main/>
      </TodosContext.Provider>
    </div>
  )
}
export default App;

=====TodosContext.js=====

import React from 'react'

const TodosContext = React.createContext({
    todos: [
        {id:1, text:'Get Grocery', complete:false},
        {id:2, text:'Excercise', complete:false},
        {id:3, text:'Drink Water', complete:true},
    ],
    currentTodo: {}
})

export default TodosContext

=====reducer.js=====

import React from 'react'

export default function reducer(state, action){
    switch(action.type){
        case "GET_TODOS":
            return {
                ...state,
                todos: action.payload
            }
        case "SET_CURRENT_TODO":
                   return {
                       ...state,
                       currentTodo: action.payload
            }
        default: 
            return state
    }
}

=====Todos.js=====

import React, {useState, useContext, useEffect} from 'react';
import TodosContext from './context'

function Todos(){   
    const [todo, setTodo] = useState("")
    const {state, dispatch} = useContext(TodosContext)
    useEffect(()=>{
        if(state.currentTodo.text){
            setTodo(state.currentTodo.text)
        } else {
            setTodo("")
        }
        dispatch({
            type: "GET_TODOS",
            payload: state.todos
        })
    }, [state.currentTodo.id])

    const editRow = event =>{
        let destUrlEdit = `/todos/${event.id}`

        let obj = {}
        obj.id = event.id
        obj.text = event.text

        dispatch({type:"SET_CURRENT_TODO", payload: obj})

        //after dispatch I would like to redirect to another route to do the actual edit
        //destUrlEdit
    }
    return(
        <div>
            <h1>List of ToDos</h1>
            <h4>{title}</h4>
            <ul>
                {state.todos.map(todo => (
                    <li key={todo.id}>{todo.text} &nbsp;
                        <button onClick={()=>{
                            editRow(todo)}}>
                        </button>
                    </li>
                ))}
            </ul>
        </div>
    )
}

export default Todos;

【问题讨论】:

    标签: javascript reactjs react-router react-router-v4 react-hooks


    【解决方案1】:

    您的问题与使用 react-router-v4 而不是钩子以编程方式导航有关,

    在 react-router-v4 中,如果 Todos 组件被渲染为子组件或 Route 或从渲染表单 Route 的祖先并将路由器 props 传递给它,您将从 props 获取历史记录。但是它没有接收到Router props,你可以使用react-router中的withRouter HOC来获取router props并调用props.history.push(destUrlEdit)

    import React, {useState, useContext, useEffect} from 'react';
    import TodosContext from './context'
    import { withRouter } from 'react-router-dom';
    
    function Todos(props){   
        const [todo, setTodo] = useState("")
        const {state, dispatch} = useContext(TodosContext)
        useEffect(()=>{
            if(state.currentTodo.text){
                setTodo(state.currentTodo.text)
            } else {
                setTodo("")
            }
            dispatch({
                type: "GET_TODOS",
                payload: state.todos
            })
        }, [state.currentTodo.id])
    
        const editRow = event =>{
            let destUrlEdit = `/todos/${event.id}`
    
            let obj = {}
            obj.id = event.id
            obj.text = event.text
    
            dispatch({type:"SET_CURRENT_TODO", payload: obj})
    
            //after dispatch I would like to redirect to another route to do the actual edit
            //destUrlEdit
            props.history.push(destUrlEdit);
        }
        return(
            <div>
                <h1>List of ToDos</h1>
                <h4>{title}</h4>
                <ul>
                    {state.todos.map(todo => (
                        <li key={todo.id}>{todo.text} &nbsp;
                            <button onClick={()=>{
                                editRow(todo)}}>
                            </button>
                        </li>
                    ))}
                </ul>
            </div>
        )
    }
    
    export default withRouter(Todos);
    

    【讨论】:

    • 感谢您的及时回复!!这为我指明了探索在 React 类组件与 React 功能组件中访问 React Router 道具的正确方向。要添加到您的答案中的一件事是,因为 固有地提供了 {history, match, location} 道具,如果路由已经定义并且位于主级别,则您不需要 HOC withRouter。道具可以按原样访问。但是如果组件嵌套很深,那么 HOC withRouter 就会变得很有帮助。
    • 我写了一篇关于此的文章:dev.to/httpjunkie/… 和一个沙盒示例:6wo1673y2r.codesandbox.io 可能会有所帮助!
    • React 日志给人一种递归状态更新正在发生的感觉,这会导致组件被卸载然后再次安装,但最终历史会在状态保持之前卸载组件,就是这样奇怪。
    • @EricBishard 我一直在寻找几个小时,就像您在文章中建议的那样简单。我认为你应该在这里发布它作为答案。
    【解决方案2】:

    使用 react-redux 和 connected-react-router...

    import {useDispatch } from 'react-redux';
    import { push } from 'connected-react-router';
    
    export default () => {
     const dispatch = useDispatch();
    
     return (
       <Button onClick={() => dispatch(push('/login'))}>
         Login
       </Button>    
      );
    };
    

    【讨论】:

    • 我收到了错误Uncaught Could not find router reducer in state tree, it must be mounted under "router",尽管在我开始使用钩子之前一切都已正确设置。使用组件类的旧方法有效。一旦我切换到这种方法,我就会得到那个错误。
    【解决方案3】:

    它实际上比其他答案简单得多,React Router v5.1 提供了一个useHistory 钩子。

    import React from 'react'
    import { useHistory } from 'react-router-dom'
    
    const MyComponent = () => {
      const history = useHistory()
      const handleButtonClick = (event) => {
        history.push(event.target.value)
      }
      return (
        <button
          type="button"
          value="/my/path"
          onClick={handleButtonClick}
        >
          Navigate Me!
        </button>
      )
    }
    

    【讨论】:

    • 我认为实际上是 v5.1 引入了这个功能:reacttraining.com/blog/react-router-v5-1
    • 是的,你是对的!我已经更新了答案中的版本。
    • history.push('/') 仍然会将用户发送到页面的旧迭代,但是,您仍然需要将其放在 useEffect 中并为 useEffect 提供一个变量检查更改,或通过 props.history.push('/') 向您的历史记录提供道具,其中道具被传递到功能组件中。
    猜你喜欢
    • 2021-12-30
    • 1970-01-01
    • 2016-04-16
    • 1970-01-01
    • 2018-11-25
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    相关资源
    最近更新 更多