Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

useReducer state change is one character delay at onChange

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?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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;

>Solution :

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>
  );
};
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading