简体   繁体   English

为什么我必须第二次发送操作来更新 state? Redux,反应,NodeJS,MySQL

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

So I'm creating a User Auth login system using MySQL, Node, React and Redux.所以我正在使用 MySQL、Node、React 和 Redux 创建一个用户身份验证登录系统。

I've created a register endpoint to insert registered users into my MySQL database.我创建了一个注册端点来将注册用户插入到我的 MySQL 数据库中。

I've then created a login endpoint to select logged in users from my MySQL database.然后,我为 select 从我的 MySQL 数据库中登录的用户创建了一个登录端点。

What my folder structure looks like我的文件夹结构是什么样的

SignUpAndSignInComponent just returns my SignUp and SignIn components. SignUpAndSignInComponent 只返回我的 SignUp 和 SignIn 组件。

My SignUp component uses Hooks to set local state which then gets passed to my backend through my register endpoint.我的注册组件使用 Hooks 设置本地 state,然后通过我的注册端点传递到我的后端。

My SignIn component also uses Hooks to set local state which then passed to my backend to retrieve a user from the database.我的 SignIn 组件还使用 Hooks 设置本地 state 然后传递到我的后端以从数据库中检索用户。

At this point I have only displayed which user is logged in but only on the SignUpAndSignIn page.此时我只显示了登录的用户,但只显示在 SignUpAndSignIn 页面上。

I want to be able to display the user logged in on my header, so I need to lift state up.我希望能够显示登录到我的 header 的用户,所以我需要提升 state。

What I did now is I brought in Redux to manage my state and keep logged in user's state in one big store so that all components can have access to it.我现在所做的是将 Redux 引入到一个大商店中来管理我的 state 并保持登录用户的 state 以便所有组件都可以访问它。

My SignIn component dispatched the action to the store using useDispatch我的 SignIn 组件使用 useDispatch 将操作分派到商店

I want to be able to take the username from the logged in user and pass it into my header component at my top-most App level.我希望能够从登录用户那里获取用户名,并将其传递到我最顶层应用程序级别的 header 组件中。

My top-most App component uses useSelector to retrieve user logged in from the store and displays it in my header.我最顶层的 App 组件使用 useSelector 检索从商店登录的用户并将其显示在我的 header 中。

I managed to do this by creating my store, rootReducer, user reducer, action and types.我设法通过创建我的商店、rootReducer、用户减速器、操作和类型来做到这一点。 I even persisted the state so that it's visible at the App level using redux-persist and local storage.我什至保留了 state 以便使用 redux-persist 和本地存储在应用程序级别可见。

My issue is that when a user logs in (clicks submit button on login form) it only updates the local state and displays state on its local view.我的问题是,当用户登录(单击登录表单上的提交按钮)时,它只会更新本地 state 并在其本地视图上显示 state。

It's only when I log in a second time that my action gets dispatched to the store so that I can view logged in user at the top level.只有当我第二次登录时,我的操作才会发送到商店,以便我可以在顶层查看登录用户。 Why is this?为什么是这样?

Do I need to use Redux-thunk to handle this asynchronous requests?我需要使用 Redux-thunk 来处理这个异步请求吗? That what I think I should do but need more clarity on what the actual issue is.这是我认为我应该做的,但需要更清楚地了解实际问题是什么。

here's what my redux logger middleware is posting in console这是我的 redux 记录器中间件在控制台中发布的内容

local view ie view within SignIn component本地视图,即登录组件中的视图

global view ie view within App.js component全局视图,即 App.js 组件中的视图

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 with useSelector带有 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;


It looks like this is due to the way you are making your async call in the login function.看起来这是由于您在登录 function 中进行异步调用的方式。 You have used async await but you are also using.then() Your CallBoth function is waiting for the login function to finish, but it is not waiting for the.then call to finish, so the isUserLoggedIn is dispatching the state before it gets updated - it gets caught on the second login which is why it's working after the second login attempt. You have used async await but you are also using.then() Your CallBoth function is waiting for the login function to finish, but it is not waiting for the.then call to finish, so the isUserLoggedIn is dispatching the state before it gets updated - 它在第二次登录时被捕获,这就是它在第二次登录尝试后工作的原因。

You could simply remove the CallBoth function and put the isUserLoggedIn call inside the.then after the setState call.您可以简单地删除 CallBoth function 并将 isUserLoggedIn 调用放在 setState 调用之后的.then 中。 Then the login function will be your on submit handler然后登录 function 将是您的提交处理程序

EDITED已编辑

In doing so, you would not need the login function to be async await.这样做,您不需要登录 function 来异步等待。 Also, you actually won't need to set the response data to the state - you can pass it straight into isUserLoggedIn.此外,您实际上不需要将响应数据设置为 state - 您可以将其直接传递给 isUserLoggedIn。 Since you are storing the data in the redux store, you don't need to store it in the component state at all.由于您将数据存储在 redux 存储中,因此您根本不需要将其存储在组件 state 中。 Just use your selector to grab it from redux只需使用您的选择器从 redux 中抓取它

You can modify your isUserLoggedIn function to accept the response data as a parameter like this:您可以修改您的 isUserLoggedIn function 以接受响应数据作为参数,如下所示:

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

then in your login function:然后在您的登录 function 中:

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);
         }

      });
   }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM