How to prevent unnecessary component re-render in react in input field on each key stroke

64 Views Asked by At

In the code shown here, when I am trying to enter character in each input field, it constantly is re-rendering the whole component as I checked in react dev tools by highlight component while state update.

Like: username input: when I pressed "abc", it re-rendered the component 3 times. That is very bad for performance.

On each input field, I am facing this issue - how to prevent that?Can you provide a solution that I can dive deeper into that? Thanks.

import React, { useState } from 'react';
import {
  CButton,
  CCard,
  CCardBody,
  CCol,
  CContainer,
  CForm,
  CFormInput,
  CInputGroup,
  CInputGroupText,
  CRow,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import axios from 'axios';
import InputMask from 'react-input-mask';
import { cilLocationPin, cilLockLocked, cilUser, cilPhone } from '@coreui/icons';
import { useNavigate } from 'react-router-dom';

const UserRegister = () => {
  console.log('customer register');
  const navigate = useNavigate();
  const [validated, setValidate] = useState(false);
  const [inputValue, setInputValue] = useState({
    userName: '',
    email: '',
    password: '',
    repeatPassword: '',
    location: '',
    phoneNumber: '',
  });
  const [error, setError] = useState({
    userName: '',
    email: '',
    password: '',
    repeatPassword: '',
    location: '',
    phoneNumber: '',
  });

  const handleInputBlur = (e) => {
    const { name, value } = e.currentTarget;
    if (name === 'userName' && (value.length > 20 || value.length < 5)) {
      setError((prevValues) => ({
        ...prevValues,
        [name]: 'input length between 5 to 20',
      }));
    } else if (name === 'location' && (value.length > 30 || value.length < 3)) {
      setError((prevValues) => ({
        ...prevValues,
        [name]: 'input length between 3 to 30',
      }));
    } else if (name === 'phoneNumber' && value.includes('_')) {
      setError((prevValues) => ({
        ...prevValues,
        [name]: 'input length must be 10',
      }));
    } else if (
      name === 'email' &&
      !/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/i.test(value)
    ) {
      setError((prevValues) => ({
        ...prevValues,
        [name]: 'enter valid email address',
      }));
    } else if (name === 'password' && value.length < 8) {
      setError((prevValues) => ({
        ...prevValues,
        [name]: 'password length is very less',
      }));
    } else if (
      name === 'repeatPassword' &&
      !(
        value.length === inputValue.password.length &&
        value === document.getElementsByName('password')[0].value
      )
    ) {
      setError((prevValues) => ({
        ...prevValues,
        [name]: 'password & repeat password must be same',
      }));
    }
  };

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setInputValue((prevValues) => ({
      ...prevValues,
      [name]: value,
    }));
    if (name === 'phoneNumber' && value.includes('_')) {
      setError((prevValues) => ({
        ...prevValues,
        [name]: 'phone number length must be 10',
      }));
    } else {
      setError((prevValues) => ({
        ...prevValues,
        [name]: null,
      }));
    }
  };

  const handleSubmit = async (e) => {
    if (
      inputValue.userName === '' ||
      inputValue.email === '' ||
      inputValue.password === '' ||
      inputValue.repeatPassword === '' ||
      inputValue.location === '' ||
      inputValue.phoneNumber === ''
    ) {
      setValidate(true);
      e.preventDefault();
      e.stopPropagation();
    } else {
      try {
        setValidate(true);
        const url = 'http://localhost:3000/api/booking/customerRegistration';
        const response = await axios.post(url, inputValue);
        // console.log('form values', inputValue);
        if (response.status === 200) {
          window.alert('registration successful');
          console.log('inputValue', inputValue);
          navigate('/login');
        } else {
          window.alert('Email Already Exist');
          console.log(response.data);
        }
      } catch (err) {
        console.log('err', err);
      }
    }
  };

  return (
    <div className="bg-light min-vh-100 d-flex flex-row align-items-center">
      <CContainer>
        <CRow className="justify-content-center">
          <CCol md={9} lg={7} xl={6}>
            <CCard className="mx-4">
              <CCardBody className="p-4">
                <CForm validated={validated}>
                  <h1>Register</h1>
                  <p className="text-medium-emphasis">Create your account</p>
                  <CInputGroup className="mb-3">
                    <CInputGroupText>
                      <CIcon icon={cilUser} />
                    </CInputGroupText>
                    <CFormInput
                      placeholder="Username"
                      name="userName"
                      value={inputValue.userName}
                      onChange={handleInputChange}
                      onBlur={handleInputBlur}
                      required
                      minLength={5}
                      maxLength={20}
                      autoComplete="username"
                      feedbackInvalid="your name is required"
                    />
                  </CInputGroup>
                  {error && error?.userName ? (
                    <div style={{ fontSize: '14px', color: '#e65a5a' }}>{error?.userName}</div>
                  ) : (
                    ''
                  )}
                  <CInputGroup className="mb-3">
                    <CInputGroupText>@</CInputGroupText>
                    <CFormInput
                      placeholder="Email"
                      type="email"
                      name="email"
                      value={inputValue.email}
                      onChange={handleInputChange}
                      onBlur={handleInputBlur}
                      required
                      autoComplete="email"
                      feedbackInvalid="email is required"
                    />
                  </CInputGroup>
                  {error && error?.email ? (
                    <div style={{ fontSize: '14px', color: '#e65a5a' }}>{error?.email}</div>
                  ) : (
                    ''
                  )}
                  <CInputGroup className="mb-3">
                    <CInputGroupText>
                      <CIcon icon={cilLockLocked} />
                    </CInputGroupText>
                    <CFormInput
                      type="password"
                      name="password"
                      placeholder="Password"
                      autoComplete="new-password"
                      value={inputValue.password}
                      minLength={8}
                      onChange={handleInputChange}
                      onBlur={handleInputBlur}
                      feedbackInvalid="password is required"
                      required
                    />
                  </CInputGroup>
                  {error && error?.password ? (
                    <div style={{ fontSize: '14px', color: '#e65a5a' }}>{error?.password}</div>
                  ) : (
                    ''
                  )}
                  <CInputGroup className="mb-4">
                    <CInputGroupText>
                      <CIcon icon={cilLockLocked} />
                    </CInputGroupText>
                    <CFormInput
                      type="password"
                      name="repeatPassword"
                      placeholder="Repeat password"
                      autoComplete="new-password"
                      value={inputValue.repeatPassword}
                      minLength={8}
                      maxLength={inputValue.password.length}
                      onChange={handleInputChange}
                      onBlur={handleInputBlur}
                      feedbackInvalid="repeat password is required"
                      required
                    />
                  </CInputGroup>
                  {error && error?.repeatPassword ? (
                    <div style={{ fontSize: '14px', color: '#e65a5a' }}>
                      {error?.repeatPassword}
                    </div>
                  ) : (
                    ''
                  )}
                  <CInputGroup className="mb-3">
                    <CInputGroupText>
                      <CIcon icon={cilLocationPin} />
                    </CInputGroupText>
                    <CFormInput
                      placeholder="Location"
                      name="location"
                      value={inputValue.location}
                      onChange={handleInputChange}
                      onBlur={handleInputBlur}
                      minLength={3}
                      maxLength={30}
                      feedbackInvalid="location is required"
                      required
                      autoComplete="location"
                    />
                  </CInputGroup>
                  {error && error?.location ? (
                    <div style={{ fontSize: '14px', color: '#e65a5a' }}>{error?.location}</div>
                  ) : (
                    ''
                  )}
                  <CInputGroup className="mb-3">
                    <CInputGroupText>
                      <CIcon icon={cilPhone} />
                    </CInputGroupText>
                    <InputMask
                      mask="99999-99999"
                      maskChar="_"
                      value={inputValue.phoneNumber}
                      onChange={handleInputChange}
                      onBlur={handleInputBlur}
                      required
                    >
                      {(inputProps) => (
                        <CFormInput
                          type="text"
                          id="phoneNumber"
                          name="phoneNumber"
                          placeholder="Enter Phone Number"
                          {...inputProps}
                          feedbackInvalid={'phone number is required'}
                        />
                      )}
                    </InputMask>
                  </CInputGroup>
                  {error && error?.phoneNumber ? (
                    <div style={{ fontSize: '14px', color: '#e65a5a' }}>{error?.phoneNumber}</div>
                  ) : (
                    ''
                  )}
                  <div className="d-grid">
                    <CButton color="success" type="button" onClick={handleSubmit}>
                      Create Account
                    </CButton>
                  </div>
                </CForm>
              </CCardBody>
            </CCard>
          </CCol>
        </CRow>
      </CContainer>
    </div>
  );
};

export default UserRegister;
2

There are 2 best solutions below

2
Owenn On

A few options that you can look into:

  1. Isolate the input component
    Put the input component into a child component, and put all the logic for input inside of that component, this way only the child component will rerender
  2. Use useRef instead of useState
    Change in ref will not cause rerenders
    https://codedamn.com/news/reactjs/a-comprehensive-guide-to-using-useref-hook
  3. Use debounce method
    Debouncing is removing unwanted input noise from buttons, switches or other user input. Debouncing prevents extra activations or slow functions from triggering too often.
    https://www.geeksforgeeks.org/lodash-_-debounce-method/
2
Saqlain Rasheed On

Instead of creating a state, we should consider using the useRef() hook which will allow us to access the input field's value directly.

In this case, we can create ref for each input field and after that, we can access the values using ref.current.value, and if you need to manually need to update the value just use ref.current.value = 'Any value goes here'.