我无法在 React 中设置状态

I can't set a state from fetch in React

我正在尝试登录并设置当前用户。问题是登录成功,数据正确但是我无法设置状态,用户为空

UserContext.js

import React, { useContext, useState } from 'react';

const UserContext = React.createContext();

export const useAuth = () => {
  return useContext(UserContext);
}

export const UserContextProvider = ( {children} ) => {
  const [ user, setUser ] = useState({
    name: '',
    lastname: '',
    username: '',
    password: ''
  });
  const [ validation, setValidation ] = useState({
    username: '',
    password: ''
  });

  const setUserData = (e) => {
    return ( {target: {value}} ) => {
      setUser(data => ( {...data, [e]: value} ));
    }
  }

  const setUserValidation = (e) => {
    return ( {target: {value}} ) => {
      setValidation(data => ( {...data, [e]: value} ));
    }
  }

  const signUp = async () => {
    return await fetch('http://localhost:8080/users/signup', {
      method: 'POST',
      body: JSON.stringify(user),
      headers: { 'Content-Type': 'application/json' }
    });
  }

  const signIn = async () => {
    return await fetch('http://localhost:8080/users/signin', {
      method: 'POST',
      body: JSON.stringify(validation),
      headers: { 'Content-Type': 'application/json' }
    }).then((res) => res.json())
      .then(data => {
        console.log(data);
      setUser({
        name: data.name,
        lastname: data.lastname,
        username: data.username,
        password: data.password
      });
      console.log(user);
    });
  }

  const signOut = async () => {
    await fetch('http://localhost:8080/users/signout');
    setUser(null);
    return;
  }

  return (
    <UserContext.Provider value={{
      user,
      setUserData,
      setUserValidation,
      signUp,
      signIn,
      signOut
    }}>
      { children }
    </UserContext.Provider>
  );
}

SignIn.js

import './SignIn.css';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/UserContext';

const SignIn = () => {
  const { signIn, setUserValidation, user } = useAuth();
  const [ errorMessage, setErrorMessage ] = useState(null);
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    setErrorMessage(null);
    
    signIn().then(() => {
      console.log(user);
      setErrorMessage(null);
      navigate('/');
    }).catch(err => {
      setErrorMessage('Error singing in, please try again.', err);
    });
  }

  return (
    <div className='SignIn'>
      <h3>Sign In</h3>
      <form className='LoginForm' onSubmit={handleSubmit}>
        { errorMessage && <h4 className='ErrorMessage'>{errorMessage}</h4> }
        <input type='email' name='username' placeholder='Email' onChange={setUserValidation('username')} required/>
        <input type='password' name='password' placeholder='Password' onChange={setUserValidation('password')} required/>
        <button className='Login' type='submit'>Sign In</button>
      </form>
      <h5>Don't have an account?</h5><Link className='Redirect' to='/signup'>Sign Up</Link>
    </div>
  );
}

export default SignIn;

As you can see, the first console.log shows the correct user information, but then is empty after the setUser() and in the SignIn.js component.

我猜这里可能发生了什么。我没有时间验证我的猜测,所以如果我说错了请原谅我。

当您更改上下文中的状态时,将重新呈现提供者及其子项。在本例中,UserContextProvider 及其子项。
所以首先,请确保 SignIn 在 UserContextProvider 中呈现,例如将所有应用程序嵌入一个 .我通常在 index.js 文件中这样做。

ReactDOM.render(
    <UserContextProvider>
        {/* ... app here, that includes SignIn ... */}
    </UserContextProvider>,
    document.getElementById('root')
);

第二件事,因为你包含 console.log() 以便在你更改状态的同一渲染中执行,很明显它们不会反映 仅在后续渲染中可用
我建议你把 console.log(user) 放在 SignIn 组件的开头,在 useNavigate() 之后说, 之外 handleSubmit 功能。

const SignIn = () => {
  const { signIn, setUserValidation, user } = useAuth();
  const [ errorMessage, setErrorMessage ] = useState(null);
  const navigate = useNavigate();

  console.log(user)

  // ...ecc...
}

如果我是对的,这个 console.log 将被执行(至少)两次,一次用于初始渲染,一次用于由 setUser 触发的后续渲染(您还可以包括一个console.log 在 handleSubmit 中只是为了检测由 setUser 触发的重新渲染。在最后的渲染中,您应该看到用户数据。

如果这按我预期的那样工作,我想你可以用这样的方式处理登录

const SignIn = () => {
  const { signIn, setUserValidation, user } = useAuth();
  const [ errorMessage, setErrorMessage ] = useState(null);
  const navigate = useNavigate();

  // in the first rendering, the userName will be '', so it won't navigate
  // if the component is re-rendered after the setUser in signIn,
  // in this rendering there will be a userName, hence the navigation will proceed
  if (user.userName !== '') {
    navigate('/');
  }

  const handleSubmit = async (e) => {
    e.preventDefault();
    setErrorMessage(null);
    
    signIn().catch(err => {
      setErrorMessage('Error singing in, please try again.', err);
    });
  }

  return (
    // ... as before ...
  );
}

编码愉快! - 卡洛斯

这是正常现象。您不能在设置状态后直接访问状态。更新后的状态将仅在下一次渲染时可用。上下文也是如此。

如果您需要在 SignIn() 之后直接访问用户数据,您可以这样做。

UserContext.js

const signIn = async () => {
  return await fetch('http://localhost:8080/users/signin', {
    method: 'POST',
    body: JSON.stringify(validation),
    headers: { 'Content-Type': 'application/json' }
  }).then((res) => res.json())
    .then(data => {
      console.log(data);
      const usr = {
        name: data.name,
        lastname: data.lastname,
        username: data.username,
        password: data.password
      }
      setUser(usr);
      // console.log(user); <-- You can't do this
      return usr // <--
    });
}

SignIn.js

const handleSubmit = async (e) => {
  e.preventDefault();
  setErrorMessage(null);

  signIn().then((usr) => { // <---
    console.log(usr);
    // console.log(user); <-- You can't do this
    setErrorMessage(null);
    navigate('/');
  }).catch(err => {
    setErrorMessage('Error singing in, please try again.', err);
  });
}