Search code examples
javascriptreactjsreact-hooks

React useCallback hook: What are the correct dependencies for these handleChange & handleSubmit functions to prevent re rendering?


I have a login component which unnecessarily re-renders. I have wrapped the component with React.memo and am using the useCallBack hooks to prevent those functions from getting created on every render if there values don't change...

Consider the following:

I have a generic FormComponent which gets one prop:

function FormComponent({
   formType
}) { ....}

export default React.memo(FormComponent)

That prop will switch between the different forms I have e.g. login, registration etc. In my example I am just showing the login form, I figure I'll just apply the solution to the others.

function FormComponent({
  formType
}) {

/* various setstate removed for brevity */

  const Forms = {
    Login: [LoginForm,
      () => loginSubmit(
        email,
        password,
        setEmail,
        setPassword,
        setFormError,
        setFormSuccess,
        setIsLoading,
        setResponseMessage,
        dispatch,
        router,
        user,
        mutate
      )
    ]
  };

  function handleChangeForUseCallBack(name, value) {
    setResponseMessage('');
    setPasswordFeedback('')
    setPasswordConfirmationFeedback('')
    setFormError(false);
    setFormSuccess(false);
    setEmailError(false);
    setPasswordError(false);
    setPasswordConfirmationError(false);
    setDisableButton(false);

    dispatch({ type: 'resetUserAccountIsVerified', })

    setEmailDup(false);


    if (value === '') setDisableButton(() => true)

    if (name === 'email') {
      setEmail(value);
    }

    if (name === 'password') {
      setPassword(value);
    }

    if (name === 'password_confirmation') {
      setPasswordConfirmation(value);
    }

    if (name === 'current_location') {
      setCurrentLocation(value);
    }

    if (name === 'current_destination') {
      setCurrentDestination(value);
    }

    if (name === 'interested_activities') {
      setInterestedActivitiesInput(value);
    }
  }

  const handleChange = useCallback((e) => {
    e.persist();
    const { name, value } = e.target;
    handleChangeForUseCallBack(name, value);
  }, [email, formType, password, password_confirmation, handleChangeForUseCallBack, setIsLoading]);

  function handleSubmitForUseCallBack(e, form) {
    e.preventDefault();
    setDisableButton(true);
    validateInputs(
      form,
      email,
      setEmailError,
      setEmailFeedback,
      password,
      password_confirmation,
      setPasswordConfirmationError,
      setPasswordConfirmationFeedback,
      setPasswordError,
      setPasswordFeedback,
      setFormSuccess,
      setFormError,
    );
    return preventSubmit ? false : Forms[form][1]()
  }

  const handleSubmit = useCallback((e, form) => {
    handleSubmitForUseCallBack(e, form);
  }, [email, password, password_confirmation, interestedActivities, handleSubmitForUseCallBack]);

  function LoginForm() {
    useEffect(() => {
      dispatch({ type: 'resetUserAccountIsVerified' })
    }, [id]);

    return (
      mounted && <GenericFormComponent
        handleSubmit={handleSubmit}
        formType={formType}
        formSuccess={formSuccess}
        formError={formError}
        accountNotVerified={accountNotVerified}
        email={email}
        emailError={emailError}
        emailFeedback={emailFeedback}
        handleChange={handleChange}
        password={password}
        passwordError={passwordError}
        passwordFeedback={passwordFeedback}
        disableButton={disableButton}
        buttonName="Log-in"
        isLoading={isLoading}
        setIsLoading={setIsLoading}
        responseMessage={responseMessage}
      />
    );
  }

  return Forms[formType][0]();
}

Is the problem the handleSubmit has a various function calls and values being passsed in which need to be passed into the useCallback dependencies?

Any help would be appreciated!


Solution

  • I don't think useCallback is the issue here. One potential cause may be the following side effect running when the LoginForm is rendered:

    useEffect(() => {
      dispatch({ type: 'resetUserAccountIsVerified' })
    }, [id]);
    

    but I may be wrong. I think the definite issue here is the fact that LoginForm is defined inside of your FormComponent. Since its defined inside of FormComponent each time FormComponentis reevaluated (from a state change, for example), LoginForm would be reinitialized, and thus if it's already rendered, then it will rerender. I think defining the LoginForm elsewhere would solve your problem.