Matthew Smith
Matthew Smith

Reputation: 21

Page not re-rendering after hook state update

The page renders the input correctly, however errors only seem to appear on the second render even though they are displayed is useForm state on the first render.

So for the password field I enter a single character and the state of useForm changes to {password: "Must be at.....} but the screen does not update to display errors.password until I enter in another character.

// nodejs library that concatenates classes
// reactstrap components
import {
  Button,
  Card,
  CardBody,
  CardHeader,
  Col,
  Container,
  Form,
  FormFeedback,
  FormGroup,
  Input,
  Row
} from "reactstrap";
import {register} from "apiCalls/AuthRequests";
import useForm from "hooks/AuthHooks";
import {registerFormValidation} from "components/validation/AuthFormValidation";

function RegisterForm(props) {
  const [loading, setLoading] = useState(false);
  const {values, handleChange, handleSubmit, errors, setErrors} = useForm(submit, registerFormValidation);

  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const accessCodeRef = useRef(null);

  async function submit() {
    setLoading(true);
    const response = await register(values.email, values.password, values.accessCode);
    if (response.ok) {
    } else if (response.status === 422) {
      if ("access_code" in response.data) {
        accessCodeRef.current.focus();
        setErrors({accessCode: response.data.access_code});
      }
      if ("email" in response.data) {
        setErrors({email: response.data.email});
        emailRef.current.focus();
      }
      if ("password" in response.data) {
        setErrors({password: response.data.password});
        passwordRef.current.focus();
      }
    }
    setLoading(false)
  }
  useEffect(() => {
    console.log(errors);
    });

    return (
      <>
        <div className="content">
          <Container className="pb-5">
            <Row>
              <Col lg="6" md="8" className="ml-auto mr-auto">
                <Card className="bg-secondary border-0">
                  <CardHeader className="bg-transparent">
                    <div className="text-center">
                      <h2>Register</h2>
                    </div>
                  </CardHeader>
                  <CardBody className="px-lg-5 py-lg-5">
                    <Form role="form" onSubmit={handleSubmit}>
                      <FormGroup>
                        <label
                          className="form-control-label"
                        >
                          Email
                        </label>
                        <Input
                          name="email"
                          type="email"
                          innerRef={emailRef}
                          value={values.email || ""}
                          onChange={handleChange}
                          invalid={!!errors.email}
                          required
                        />
                        <FormFeedback>{errors.email}</FormFeedback>
                      </FormGroup>
                      <FormGroup>
                        <label
                          className="form-control-label"
                        >
                          Password
                        </label>
                        <Input
                          name="password"
                          type="password"
                          innerRef={passwordRef}
                          value={values.password || ""}
                          onChange={handleChange}
                          invalid={!!errors.password}
                          required
                        />
                        <FormFeedback>{errors.password}</FormFeedback>
                      </FormGroup>
                      <FormGroup>
                        <label
                          className="form-control-label"
                        >
                          Access Code
                        </label>
                        <Input
                          name="accessCode"
                          type="text"
                          innerRef={accessCodeRef}
                          value={values.accessCode || ''}
                          onChange={handleChange}
                          invalid={!!errors.accessCode}
                          required
                        />
                        <FormFeedback>{errors.accessCode}</FormFeedback>
                      </FormGroup>
                      <Row className="my-4">
                        <Col xs="12">
                          <div
                            className="custom-control custom-control-alternative custom-checkbox">
                            <input
                              className="custom-control-input"
                              id="customCheckRegister"
                              type="checkbox"
                              required
                            />
                            <label
                              className="custom-control-label"
                              htmlFor="customCheckRegister"
                            >
                            <span className="text-muted">
                              I agree with the{" "}
                              <a
                                href=""
                                target="_blank"
                                rel="noopener noreferrer"
                              >
                                Privacy Policy
                              </a>
                            </span>
                            </label>
                          </div>
                        </Col>
                      </Row>
                      <div className="text-center">
                        <Button disabled={loading} className="mt-4" color="info" type="submit">
                          Create account
                        </Button>
                      </div>
                    </Form>
                  </CardBody>
                </Card>
              </Col>
              <Col md="4" className="ml-auto mr-auto">
                <h2>Being a photographer is easier with <b className="text-primary">FOCAL</b></h2>
                <ul>
                  <li>
                    <h4>Focal is great</h4>
                  </li>
                  <li>
                    <h4>Save time</h4>
                  </li>
                  <li>
                    <h4>More customers</h4>
                  </li>
                </ul>
              </Col>
            </Row>
          </Container>
        </div>
      </>
    );
}

export default RegisterForm;

const useForm = (callback, validate) => {

  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);


  const handleSubmit = (event) => {
    if (event) event.preventDefault();
    setIsSubmitting(true);
  };

  useEffect(() => {
    if (Object.keys(errors).length === 0 && isSubmitting) {
      callback();
      setIsSubmitting(false);
    }
  }, [callback, errors, isSubmitting]);

  useEffect(() => {
    setErrors(validate(values, errors));
  }, [validate, values, errors]);

  const handleChange = (event) => {
    event.persist();
    setValues(values => ({...values, [event.target.name]: event.target.value}));
    setErrors(validate(values, errors));
  };

  return {
    handleChange,
    handleSubmit,
    setErrors,
    values,
    errors
  }
};

export default useForm;

export function registerFormValidation(values, errors) {
  if (values.email === ""){
    delete errors.email;
  }
  if (values.password) {
    if (!verifyLength(values.password, PASSWORD_LENGTH)){
      errors.password = "Password must be greater than 8 Characters";
    } else {
      delete errors.password;
    }
  }

  if (values.accessCode === "") {
    delete values.accessCode;
  }
  return errors
}```


Upvotes: 1

Views: 274

Answers (3)

Matthew Smith
Matthew Smith

Reputation: 21

FIXED:

const useForm = (callback, validate) => {

  const [values, setValues] = useState({});
  const [errors, setErrors] = useState({});


  const handleSubmit = (event) => {
    if (event) event.preventDefault();
    if (Object.keys(errors).length === 0){
      callback();
    }
  };

  const handleChange = (event) => {
    event.persist();
    setValues(values => ({...values, [event.target.name]: event.target.value}));
    setErrors(validate({[event.target.name]: event.target.value}, errors));
  };

  return {
    handleChange,
    handleSubmit,
    setErrors,
    values,
    errors
  }
};

export default useForm;

got rid of useEffect!

Upvotes: 0

timbo
timbo

Reputation: 14314

I can appreciate that it's of interest to work on a custom hook but forms are ubiquitous and there are solid, proven means to work with them. IMHO Formik is probably the best.

With the three fields that you have there, you could implement something like the following:

import React from 'react';
import { Formik } from 'formik';

const BasicExample = () => (
  <div>
    <Formik
      initialValues={{ email: '', password: '', accessCode: '' }}
      onSubmit={(values, actions) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          actions.setSubmitting(false);
        }, 1000);
      }}
      render={props => (
        <form onSubmit={props.handleSubmit}>
          <input
            type="email"
            onChange={props.handleChange}
            onBlur={props.handleBlur}
            value={props.values.email}
            name="email"
          />
          {props.errors.email && <div id="feedback">{props.errors.email}</div>}
          <input
            type="password"
            onChange={props.handleChange}
            onBlur={props.handleBlur}
            value={props.values.password}
            name="password"
          />
          {props.errors.password && <div id="feedback">{props.errors.password}</div>}
          <input
            type="text"
            onChange={props.handleChange}
            onBlur={props.handleBlur}
            value={props.values.accessCode}
            name="accessCode"
          />
          {props.errors.accessCode && <div id="feedback">{props.errors.acessCode}</div>}
          <button type="submit">Submit</button>
        </form>
      )}
    />
  </div>
);

Note that the above is just from memory - which to some extent speaks to it's simplicity. As initialValues are supplied to Formik, you can build complex component hierarchies without having to pass down form state but that's just one of the various benefits.

Upvotes: 1

kirsanv43
kirsanv43

Reputation: 358

I have an assumption.

In registerFormValidation doesn't modify errors object. Create a new each time.

Upvotes: 0

Related Questions