reactjs useState变量在击键时未更改

t1qtbnec  于 2022-12-12  发布在  React
关注(0)|答案(1)|浏览(142)

我有一个从FormInput组件Map生成的登录表单。
我现在已经根据属性定义的问题类型创建了单独的FormComponents。
但是,由于进行了此更改,我的“凭据”状态不会在用户击键时更新。
我希望凭据状态在击键时更新。但是,目前没有发生这种情况。
登录表单

import React from 'react';
import FormInput from '../../components/Form Input/FormInput';
import { loginInputs } from '../../formSource/formSourceData';
import './Login.scss';
import { useState, useContext } from 'react';
import { useNavigate, Link, useLocation } from 'react-router-dom';
import { useToastContext } from '../../context/toastContext';
import useFetch from '../../Hooks/UseFetch';
import { AuthContext } from '../../context/AuthContext'
import axios from 'axios'

const Login = () => {

  const [credentials, setCredentials] = useState({
    email: undefined,
    password: undefined,
  });

  const { user, loading, error, dispatch} = useContext(AuthContext)

  const { toastDispatch } = useToastContext();
  const navigate = useNavigate();
  const { state } = useLocation()

  const handleChange = (e) => {
    console.log(e.target.name)
    setCredentials({ ...credentials, [e.target.name]: e.target.value });
    console.log(credentials)
  };

  const handleSubmit = async (e) => {
    e.preventDefault()
    dispatch({type: "LOGIN_START"})
    try{
      const res = await axios.post("/api/auth/login", credentials)
      dispatch({type:"LOGIN_SUCCESS", payload: res.data})
      navigate(state?.path || '/auth/teacher/dashboard');
    } catch(err) {
      dispatch({type: "LOGIN_FAILURE", payload: err.response.data})
    }
  }

  return (
    <div className="container">
      <div className="formWrapper">
        <h1>Login</h1>
        <form className="loginForm" >
          {loginInputs.map((input) => (
            <FormInput
              key={input.id}
              {...input}
              handleChange={handleChange}
            />
          ))}
          <button type="submit" className="loginButton" onClick={handleSubmit}>
            Login
          </button>
        </form>
        <p className="forgotPassword">
          <Link to="/forgot-password">Forgot Password</Link>
        </p>
        <p className="accountText">
          Not signed up? <Link to="/register">Register</Link>
        </p>
      </div>
    </div>
  );
};

export default Login;

表单输入组件

import { InputRounded } from '@mui/icons-material';
import React, { useState } from 'react';
import './formInput.scss';

const FormInput = (props) => {
  const { label, type, errorMessage, handleChange, id, value, ...inputProps } =
    props;

  const [focused, setFocused] = useState(false);
  const [passwordShown, setPasswordShown] = useState(false);

  const handleFocus = (e) => {
    setFocused(true);
  };

  const togglePassword = () => {
    setPasswordShown(!passwordShown);
  };

  const Dropdown = () => {
    return (
      <select
        className="formElementInput"
        value={value}
        name={inputProps.name}
        onChange={handleChange}
      >
        <option className="default" selected disabled>
          {inputProps.placeholder}
        </option>
        {inputProps.options.map((option) => (
          <option className="option" value={option}>
            {option}
          </option>
        ))}
      </select>
    );
  };

  const Input = () => {
    return (
      <div className="formGroup">
        <input
          className="formElementInput"
          value={value}
          name={props.name}
          placeholder={props.placeholder}
          type={passwordShown ? 'text' : type}
          onChange={props.handleChange}
          onBlur={handleFocus}
          focused={focused.toString()}
          onFocus={() =>
            inputProps.name === 'confirmPassword' && setFocused(true)
          }
        />
        <span className="icon" onClick={togglePassword}>
          {passwordShown ? inputProps.icon : inputProps.opposite}
        </span>
      </div>
    );
  };

  return (
    <div className="formElement">
      <label className="formElementLabel">{label}</label>
      {type === 'dropdown' ? (
        <Dropdown />
      ) : (
        <Input/>
      )}

      <span className="errorMessage">{errorMessage}</span>
    </div>
  );
};

export default FormInput;

登录输入代码

export const loginInputs = [
    {
        id: 1,
        name: "email",
        type: "email",
        placeholder: "Email",
        label: "Email",
        errorMessage: "Enter a valid email address",
        required: true

    },
    {
        id: 2,
        name: "password",
        type: "password",
        placeholder: "Password",
        label: "Password",
        errorMessage: "A password should be more than 8 characters.",
        required: true,
        icon: <Visibility/>,
        opposite: <VisibilityOff/>

    }
]

这是分离前的原始工作代码。

import { InputRounded } from '@mui/icons-material';
import React, { useState } from 'react';
import './formInput.scss';

const FormInput = (props) => {
  const { label, type, errorMessage, handleChange, id, value, ...inputProps } =
    props;

  const [focused, setFocused] = useState(false);
  const [passwordShown, setPasswordShown] = useState(false);

  const handleFocus = (e) => {
    setFocused(true);
  };

  const togglePassword = () => {
    setPasswordShown(!passwordShown);
  };

  const Dropdown = () => {
    return (
      <select
        className="formElementInput"
        value={value}
        name={inputProps.name}
        onChange={handleChange}
      >
        <option className="default" selected disabled>
          {inputProps.placeholder}
        </option>
        {inputProps.options.map((option) => (
          <option className="option" value={option}>
            {option}
          </option>
        ))}
      </select>
    );
  };

  const Input = () => {
    return (
      <div className="formGroup">
        <input
          className="formElementInput"
          value={value}
          name={props.name}
          placeholder={props.placeholder}
          type={passwordShown ? 'text' : type}
          onChange={props.handleChange}
          onBlur={handleFocus}
          focused={focused.toString()}
          onFocus={() =>
            inputProps.name === 'confirmPassword' && setFocused(true)
          }
        />
        <span className="icon" onClick={togglePassword}>
          {passwordShown ? inputProps.icon : inputProps.opposite}
        </span>
      </div>
    );
  };

  return (
    <div className="formElement">
      <label className="formElementLabel">{label}</label>
      {type === 'dropdown' ? (

        <select
          className="formElementInput"
          value={value}
          name={inputProps.name}
          onChange={handleChange}
        >
          <option className="default" selected disabled>
            {inputProps.placeholder}
          </option>
          {inputProps.options.map((option) => (
            <option className="option" value={option} >
              {option}
            </option>
          ))}
        </select>
      ) : (
        <div className="formGroup">
          <input
            className="formElementInput"
            value={value}
            name={inputProps.name}
            placeholder={inputProps.placeholder}
            type={passwordShown ? "text" : type}
            onChange={handleChange}
            onBlur={handleFocus}
            focused={focused.toString()}
            onFocus={() =>
              inputProps.name === 'confirmPassword' && setFocused(true)
            }
          />
          <span className="icon" onClick={togglePassword}>
              {passwordShown ? inputProps.icon : inputProps.opposite}
          </span> 
          </div>
      )}

      <span className="errorMessage">{errorMessage}</span>
    </div>
  );
};

export default FormInput;

这里有一个代码沙箱的链接,它展示了我上面解释过的相同行为。
https://codesandbox.io/s/thirsty-feather-9fwwwy?file=/src/App.js

fhg3lkii

fhg3lkii1#

原因是FormComponent.jsx中的<Input />组件是受控的..因此它在呈现的输入字段中查找value属性,但找不到任何属性:

return (
      <div className="formGroup">
        <input
          className="formElementInput"
          name={props.name}
          value={props.value}
          placeholder={props.placeholder}
          type={passwordShown ? "text" : props.type}
          onChange={props.handleChange}
          onBlur={handleFocus}
          focused={focused.toString()}
          onFocus={() => props.name === "confirmPassword" && setFocused(true)}
        />
        // ...

因为..没有任何:

// Notice no value `key` on any of these objects:
export const loginInputs = [
  {
    id: 1,
    name: "email",
    type: "email",
    placeholder: "Email",
    label: "Email",
    errorMessage: "Enter a valid email address",
    required: true
  },
  {
    id: 2,
    name: "password",
    type: "password",
    placeholder: "Password",
    label: "Password",
    errorMessage: "A password should be more than 8 characters.",
    required: true,
    icon: "Icon",
    opposite: "Icon2"
  }
];

要解决这个问题,可以根据credentials状态提供一个value属性:

{loginInputs.map((input) => (
  <FormInput
    key={input.id}
    value={credentials[input.name] ?? ""} // provide value prop
    {...input}
    handleChange={handleChange}
  />
))}

额外的好处:我注意到每次你在那里输入时,输入都会失去焦点(也许这不是你代码中的问题,只是你提供的沙箱中的问题)。
这是因为您在<FormInput />中声明了<Input />,因此每次它发生更改时,它基本上都是从头开始创建<Input />组件。有很多方法可以实现这一点,但最简单的方法是以三进制内联方式使用它:

{props.type === "dropdown" ? (
        <Dropdown /> // Also do the same for `Dropdown` if  face the similar isssue
      ) : (
        // used inline
        <div className="formGroup">
          <input
            className="formElementInput"
            name={props.name}
            value={props.value}
            placeholder={props.placeholder}
            type={passwordShown ? "text" : props.type}
            onChange={props.handleChange}
            onBlur={handleFocus}
            focused={focused.toString()}
            onFocus={() => props.name === "confirmPassword" && setFocused(true)}
          />
          <span className="icon" onClick={togglePassword}>
            {passwordShown ? props.icon : props.opposite}
          </span>
        </div>
      )}

下面是更新的Sandbox的链接:
https://codesandbox.io/s/epic-poitras-84z4do?file=/Login.jsx:943-995

相关问题