Home > OS >  useReducer state change is one character delay at onChange
useReducer state change is one character delay at onChange

Time:12-29

When I try to use useReducer as state management but the problem arises when I use onChange with the reducer it is one character delay. There is a useEffect that depends on the character changes to turn the validity value true or false.

But it works fine with the onBlur.

What should be the cause of this delay?

How can I solve this issue?

import React, { useState, useEffect, useReducer, useContext } from "react";
import AuthContext from "../store/auth-context";
import Button from "../UI/Button";
import Card from "../UI/Card";
import Input from "../UI/Input";

const reducerFn = (state, action) => {
  if (action.type === "Email") {
    return {
      ...state,
      emailState: action.value,
    };
  } else if (action.type === "Email_IsValid") {
    return {
      ...state,
      isEmailValid: true,
    };
  } else if (action.type === "Email_IsInValid") {
    return {
      ...state,
      isEmailValid: false,
    };
  } else if (action.type === "Password") {
    return {
      ...state,
      passwordState: action.value,
    };
  } else if (action.type === "Password_IsValid") {
    return {
      ...state,
      isPasswordIsValid: true,
    };
  } else if (action.type === "Password_IsInValid") {
    return {
      ...state,
      isPasswordIsValid: false,
    };
  }
  return { emailState: "", isEmailValid: false, passwordState: "", isPasswordIsValid: false };
};

const Login = () => {
  const [formState, dispatchFn] = useReducer(reducerFn, { emailState: "", isEmailValid: false, passwordState: "", isPasswordIsValid: false });
  const { emailState, isEmailValid, passwordState, isPasswordIsValid } = formState;
  const [isEmailValidated, setIsEmailValidated] = useState(null);
  const [isPasswordIsValidated, setIsPasswordIsValidated] = useState(null);
  const authCtx = useContext(AuthContext);
  useEffect(() => {
    if (isEmailValid) setIsEmailValidated(true);
    if (isPasswordIsValid) setIsPasswordIsValidated(true);
  }, [isEmailValid, isPasswordIsValid]);

  const onEmailChangeHandler = ({ target }) => {
    console.log(isEmailValid, isEmailValidated);
    dispatchFn({ type: "Email", value: target.value });
    if (emailState.includes("@")) {
      dispatchFn({ type: "Email_IsValid" });
    } else {
      dispatchFn({ type: "Email_IsInValid" });
      setIsEmailValidated(false);
    }
  };
  const onEmailBlurHandler = () => {
    if (emailState.includes("@")) {
      dispatchFn({ type: "Email_IsValid" });
    } else {
      dispatchFn({ type: "Email_IsInValid" });
      setIsEmailValidated(false);
    }
  };
  const onPasswordChangeHandler = ({ target }) => {
    dispatchFn({ type: "Password", value: target.value });
    if (passwordState.length > 7) {
      dispatchFn({ type: "Password_IsValid" });
    } else {
      dispatchFn({ type: "Password_IsInValid" });
      setIsPasswordIsValidated(false);
    }
    console.log(isPasswordIsValid);
  };
  const onPasswordBlurHandler = () => {
    if (passwordState.length > 7) {
      dispatchFn({ type: "Password_IsValid" });
    } else {
      dispatchFn({ type: "Password_IsInValid" });
      setIsPasswordIsValidated(false);
    }
  };
  const onFormSubmit = (e) => {
    e.preventDefault();
    if (isEmailValid === false) setIsEmailValidated(false);
    else if (isPasswordIsValid === false) setIsPasswordIsValidated(false);
    else if (isEmailValid && isPasswordIsValid) authCtx.onLogin(emailState, passwordState);
  };
  return (
    <Card>
      <form>
        <Input
          id="email"
          label="E-Mail"
          type="email"
          onChange={onEmailChangeHandler}
          onBlur={onEmailBlurHandler}
          value={emailState}
          isValidated={isEmailValidated}
          warningText="Please enter a valid email; must contains '@'"
        />
        <Input
          id="password"
          label="Password"
          type="password"
          onChange={onPasswordChangeHandler}
          onBlur={onPasswordBlurHandler}
          value={passwordState}
          isValidated={isPasswordIsValidated}
          warningText="Password must have 8 characters long"
        />
        <Button label="Login" onClick={onFormSubmit} classes={`bgRed bgWider`}></Button>
      </form>
    </Card>
  );
};

export default Login;

CodePudding user response:

You'd have a much simpler time not using reducers here at all, like so:

import React from "react";

const Login = () => {
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");
  const [shouldValidateEmail, setShouldValidateEmail] = React.useState(false);
  const [shouldValidatePassword, setShouldValidatePassword] = React.useState(false);
  const emailIsValid = email.includes("@");
  const passwordIsValid = password.length > 7;
  const errors = [
    shouldValidateEmail && !emailIsValid ? "Email invalid" : null,
    shouldValidatePassword && !passwordIsValid ? "Password invalid" : null,
  ]
    .filter(Boolean)
    .join(", ");

  return (
    <form>
      Email
      <input
        id="email"
        type="email"
        onChange={(e) => setEmail(e.target.value)}
        onBlur={() => setShouldValidateEmail(true)}
        value={email}
      />
      <br />
      Password
      <input
        id="password"
        type="password"
        onChange={(e) => setPassword(e.target.value)}
        onBlur={(e) => setShouldValidatePassword(true)}
        value={password}
      />
      <br />
      {errors ? <>Errors: {errors}</> : null}
    </form>
  );
};

export default function App() {
  const [resetKey, setResetKey] = React.useState(0);
  return (
    <div className="App">
      <Login key={resetKey} />
      <hr />
      <button onClick={() => setResetKey((k) => k   1)}>Reset</button>
    </div>
  );
}

However, if you really do want to use a reducer, just couple the validation state to when the state is changed:

function validateEmail(email) {
  return email.includes("@");
}

function validatePassword(password) {
  return password.length > 7;
}

const initialState = {
  emailState: "",
  isEmailValid: false,
  passwordState: "",
  isPasswordValid: false,
};

const reducerFn = (state, action) => {
  if (action.type === "Email") {
    return {
      ...state,
      emailState: action.value,
      isEmailValid: validateEmail(action.value),
    };
  } else if (action.type === "Password") {
    return {
      ...state,
      passwordState: action.value,
      isPasswordValid: validatePassword(action.value),
    };
  }
  return initialState;
};

const Login = () => {
  const [{ emailState, isEmailValid, passwordState, isPasswordValid }, dispatchFn] = React.useReducer(
    reducerFn,
    initialState,
  );
  const errors = [
    emailState && !isEmailValid ? "Email invalid" : null,
    passwordState && !isPasswordValid ? "Password invalid" : null,
  ]
    .filter(Boolean)
    .join(", ");

  return (
    <form>
      Email
      <input
        id="email"
        type="email"
        onChange={(e) => dispatchFn({ type: "Email", value: e.target.value })}
        value={emailState}
      />
      <br />
      Password
      <input
        id="password"
        type="password"
        onChange={(e) => dispatchFn({ type: "Password", value: e.target.value })}
        value={passwordState}
      />
      <br />
      {errors ? <>Errors: {errors}</> : null}
    </form>
  );
};
  • Related