首页 > 解决方案 > 为什么我必须第二次发送一个动作来更新状态?Redux、React、NodeJS、MySQL

问题描述

所以我正在使用 MySQL、Node、React 和 Redux 创建一个用户身份验证登录系统。

我创建了一个注册端点来将注册用户插入到我的 MySQL 数据库中。

然后我创建了一个登录端点来从我的 MySQL 数据库中选择登录用户。

我的文件夹结构是什么样的

SignUpAndSignInComponent 只返回我的 SignUp 和 SignIn 组件。

我的注册组件使用 Hooks 设置本地状态,然后通过我的注册端点传递到我的后端。

我的登录组件还使用 Hooks 设置本地状态,然后将其传递给我的后端以从数据库中检索用户。

此时我只显示了登录的用户,但只显示在 SignUpAndSignIn 页面上。

我希望能够在我的标题上显示用户登录,所以我需要提升状态。

我现在所做的是引入 Redux 来管理我的状态并在一个大商店中保持登录用户的状态,以便所有组件都可以访问它。

我的 SignIn 组件使用 useDispatch 将操作分派到商店

我希望能够从登录用户那里获取用户名,并将其传递到我最顶层应用程序级别的标题组件中。

我最顶层的 App 组件使用 useSelector 来检索从商店登录的用户并将其显示在我的标题中。

我设法通过创建我的商店、rootReducer、用户减速器、操作和类型来做到这一点。我什至使用 redux-persist 和本地存储保持状态,以便在应用程序级别可见。

我的问题是,当用户登录(单击登录表单上的提交按钮)时,它只会更新本地状态并在其本地视图上显示状态。

只有当我第二次登录时,我的操作才会发送到商店,以便我可以在顶层查看登录用户。为什么是这样?

我需要使用 Redux-thunk 来处理这个异步请求吗?这是我认为我应该做的,但需要更清楚地了解实际问题是什么。

这是我的 redux 记录器中间件在控制台中发布的内容

本地视图,即登录组件中的视图

全局视图,即 App.js 组件中的视图

带调度的登录组件表单

``` import React, { useState, useCallback } from 'react';
import Axios from 'axios';
import { useSelector, useDispatch } from 'react-redux';
import { setCurrentUser } from '../../redux/user/user.reducer';
import { Form, Button } from 'react-bootstrap';
import { UserActionTypes } from '../../redux/user/user.types';

const SignIn = () => {

   const dispatch = useDispatch();

   const [emailLog, setEmailLog] = useState('');
   const [passwordLog, setPasswordLog] = useState('');

   const [loginStatus, setLoginStatus] = useState('');
   
   const CallBoth = async () => {
      await login();
      isUserLoggedIn();
   }

   const login = async () => {
      await Axios.post('http://localhost:3001/login', {
         email: emailLog,
         password: passwordLog 
      }).then((response) => {
         
         if (response.data.message) {
            setLoginStatus(response.data.message);
         } else {
            setLoginStatus(response.data[0].username);
         }

      });
   }

   const isUserLoggedIn = () => {
      dispatch({type: UserActionTypes.SET_CURRENT_USER,
         payload: loginStatus
      });
   }



   window.onbeforeunload = function (e) {
      console.log(e);
      return true;
  }

      return(
         <div className="sign-in">
         <Form onSubmit={CallBoth} >
               <Form.Group controlId="formBasicEmail">
                  <Form.Label>Email address</Form.Label>
                  <Form.Control type="email" placeholder="Enter email"
                  onChange={
                     (e) => {setEmailLog(e.target.value);
                     }}
                  />
                  <Form.Text className="text-muted">
                     We'll never share your email with anyone else.
                  </Form.Text>
               </Form.Group>
   
               <Form.Group controlId="formBasicPassword">
                  <Form.Label>Password</Form.Label>
                  <Form.Control type="password" placeholder="Password"
                  onChange={
                     (e) => {setPasswordLog(e.target.value);
                     }}
                  />
               </Form.Group>
               <Form.Group controlId="formBasicCheckbox">
                  <Form.Check type="checkbox" label="Check me out" />
               </Form.Group>
               <Button variant="primary" type="submit"
               
               >
                  Submit
               </Button>
           </Form>
           <h1>{loginStatus}</h1>
         </div>
      )
}

export default SignIn; ```

带有 useSelector 的 App.js

```import React, { Component, useEffect } from 'react';
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";

import HomePage from './pages/homepage/homepage.component';
import SignUpAndSignIn from './pages/signupandsignin/signupandsignin.component';
import Header from './components/header/header.component';

import { useSelector, useDispatch } from 'react-redux';

function App() {

  const userLoggedIn = useSelector( (state) => state.user.currentUser);

  

  return (
    <>
    <Router>
      <Header />
      <h1>User logged in: {userLoggedIn}</h1>
      <Switch>
        <Route exact path="/" component={HomePage}>
          <HomePage />
        </Route>
        <Route path="/signupandsignin" component={SignUpAndSignIn}>
          <SignUpAndSignIn />
        </Route>
      </Switch>
    </Router>



    </>
  );
}
export default App;


标签: javascriptreactjsreact-reduxaxiosredux-thunk

解决方案


看起来这是由于您在登录功能中进行异步调用的方式。您已经使用了异步等待,但您也在使用 .then() 您的 CallBoth 函数正在等待登录函数完成,但它没有等待 .then 调用完成,因此 isUserLoggedIn 正在调度状态更新之前- 它在第二次登录时被捕获,这就是它在第二次登录尝试后工作的原因。

您可以简单地删除 CallBoth 函数并将 isUserLoggedIn 调用放在 setState 调用之后的 .then 中。然后登录功能将是您的提交处理程序

已编辑

这样做时,您不需要将登录功能设为异步等待。此外,您实际上不需要将响应数据设置为状态 - 您可以将其直接传递给 isUserLoggedIn。由于您将数据存储在 redux 存储中,因此您根本不需要将其存储在组件状态中。只需使用您的选择器从 redux 中获取它

您可以修改您的 isUserLoggedIn 函数以接受响应数据作为参数,如下所示:

const isUserLoggedIn = (loginStatus) => {
      dispatch({type: UserActionTypes.SET_CURRENT_USER,
         payload: loginStatus
      });
   }

然后在您的登录功能中:

const login = () => {
      Axios.post('http://localhost:3001/login', {
         email: emailLog,
         password: passwordLog 
      }).then((response) => {
         
         if (response.data.message) {
            isUserLoggedIn(response.data.message);
         } else {
            isUserLoggedIn(response.data[0].username);
         }

      });
   }

推荐阅读