为什么我必须第二次发送一个动作来更新状态? Redux、React、NodeJS,MySQL

Why do I have to dispatch an action a second time to update state? Redux, React, NodeJS, MySQL

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

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

然后我创建了一个登录端点,用于 select 从我的 MySQL 数据库登录的用户。

What my folder structure looks like

SignUpAndSignInComponent 只是 returns 我的注册和登录组件。

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

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

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

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

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

我的 SignIn 组件使用 useDispatch

将操作发送到商店

我希望能够从登录用户那里获取用户名并将其传递到我的 top-most 应用级别的 header 组件中。

我的 top-most 应用程序组件使用 useSelector 检索从商店登录的用户并将其显示在我的 header.

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

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

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

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

here's what my redux logger middleware is posting in console

local view i.e. view within SignIn component

global view i.e. view within App.js component

SignIn Component form with dispatch

``` 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; ```

App.js 与 useSelector

```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;


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

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

已编辑

这样一来,您就不需要异步等待登录函数了。此外,您实际上不需要将响应数据设置为状态 - 您可以将其直接传递给 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);
         }

      });
   }