TimLavelle
TimLavelle

Reputation: 81

React hook is fired on every key press, supposed to only fire with onChange

I've written a hook for a Formik form I'm creating so that on a specific field, whenever a Selection option is selected, I want the hook to be called.

But what is happening, is that in every form field, on every key press, my hook is being called. Not sure what I'm doing wrong, but would love any pointers on what I can do to only call the hook on the select change.

Here's my displayProvinces.js hook

import React from 'react';
import GetDistrictsAPI from '@/utils/services/GetDistrictsAPI';
import GetProvincesAPI from '@/utils/services/GetProvincesAPI';
import {Field, useFormikContext } from 'formik';

export default function DisplayProvinces(){
  
  const [ provinces ] = GetProvincesAPI();
  const handleSelectedProvince = useProvinces();
  
  function useProvinces(){
    console.log('Fired')
    function handleSelectedProvince(pr) {
      console.log('Selected Province = ' +pr)
      let districts = GetDistrictsAPI(pr);
    }
    
    return handleSelectedProvince;
  }
  
  const {setFieldValue} = useFormikContext();
  
  return (
    <Field
      as="select"
      id="pxProvince"
      name="pxProvince"
      onChange={(e)=> {
        handleSelectedProvince(e.target.value)
        setFieldValue('pxProvince', e.target.value)
      }}
      className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
    >
      {!provinces ? provinces : provinces.map((p, i) => (
        <option key={i} value={p.prov_id}>{p.prov_name}</option>
      ))}
    </Field>
  )
}

In this code snippet, I can see 'Fired' in the console, for every key press, even though it is only supposed to be called from the associated select onChange event.

Thanks in advance for any pointers!

And this is how I'm calling the component

<div className="col-span-6 sm:col-span-6 lg:col-span-2">
 <CVLabel field="forms.rego.fields.px.province" required='1' />
 <DisplayProvinces />
</div>

Upvotes: 1

Views: 1218

Answers (2)

Anurag Tripathi
Anurag Tripathi

Reputation: 1094

On every onChange event, you're setting up the state(setFieldValue) which triggers react to re-evaluate and re-render the component, As the re-evaluation happens react again execute useProvinces() method which logs "Fired" in the console, and returns handleSelectedProvince reference.

There're two ways to solve your problem, either use useMemo() hooks which memo-ized the value returned by the passed function and recalculate the value when any dependency changes.

Or you can tweak in the code like this:

import React from 'react';
import GetDistrictsAPI from '@/utils/services/GetDistrictsAPI';
import GetProvincesAPI from '@/utils/services/GetProvincesAPI';
import {Field, useFormikContext } from 'formik';

export default function DisplayProvinces(){
  
  const [ provinces ] = GetProvincesAPI();
  function handleSelectedProvince(pr) {
    console.log("Fired!!")
    console.log('Selected Province = ' +pr)
    let districts = GetDistrictsAPI(pr);
  }
  
  const {setFieldValue} = useFormikContext();
  
  return (
    <Field
      as="select"
      id="pxProvince"
      name="pxProvince"
      onChange={(e)=> {
        handleSelectedProvince(e.target.value)
        setFieldValue('pxProvince', e.target.value)
      }}
      className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
    >
      {!provinces ? provinces : provinces.map((p, i) => (
        <option key={i} value={p.prov_id}>{p.prov_name}</option>
      ))}
    </Field>
  )
}

Upvotes: 1

Bernhard Josephus
Bernhard Josephus

Reputation: 715

I think it happens because the re-rendering occurs. Every time your DisplayProvinces component re-render, it will call the useProvinces() function which will log Fired. Try to use useMemo hook to prevent your function getting re-initialized every component renders.

const handleSelectedProvince = useMemo(() => useProvinces(), []);

It will save the results of the useProvinces (which is a handleSelectedProvince function) even the component re-renders. More info about useMemo here

Upvotes: 1

Related Questions