I have a login form made from a map of a FormInput component.
I have now created separate FormComponents depending on the question type as defined by a prop.
However, since making this change my 'credentials' state is not updating on a user keystroke.
I would like the CREDENTIALS state to be updated on keystroke. However, this is not happening currently.
LoginForm
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;
FormInputComponent
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;
LOGIN INPUT CODE
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/>
    }
]
This is the original working code prior to separating.
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;
Here is a link to the code sandbox which exhibits the same behaviour I have explained above.
https://codesandbox.io/s/thirsty-feather-9fwwwy?file=/src/App.js

 
    