jjlittle
jjlittle

Reputation: 77

How to make a dynamic watch()/usewatch() with react-hook-forms

I am having an issue were I need to generate a form from a schema and have fields dependent on each other such as enabled/disable show/hide etc.

my schema looks something like this.

  contractorInformation: {
    title: "ContractorInformation",
    type: "object",
    properties: {
      contractorName: {
        title: "Contractor Name",
        type: "string",
        ui: "input",
        constraints: {
          required: true
        }
      },
      contractorFavouriteAnimal: {
        title: "Contractor Favourite Animal",
        type: "string",
        ui: "input",
        constraints: {},
        dependencies: {
          disabled: true,
          watching: "contractorName"
          // //disabled={!fullName || fullName.length === 0 || errors.fullName}
        }
      },
      contractorEmail: {
        title: "Contractor Email",
        type: "string",
        ui: "input",
        constraints: {
          common: 10
        }
      }
    }
  },
  agencyInformation: {
    title: "ContractorInformation",
    type: "object",
    properties: {
      contractorName: {
        title: "Contractor Name",
        type: "string",
        ui: "input",
        constraints: {
          required: true,
          minLength: 3,
          maxLength: 5
        },
        dependencies: {
          disabled: true,
          watching: "agencyName"
          
        }
      }
    }
  }
}

const watchThese = watch() would allow me to watch everything but if I wanted to change this field from disabled to enabled I could use <input disabled={!watchThese || watchThese.length === 0 || watchThese.fullName}/> which works but is obviously triggered by every field.

How could I generate a dynamic watch()/useWatch() from a schema and be able to access the specific dependencies I need to enable/disable the input.

Any help would be gratefully received.

Upvotes: 1

Views: 4156

Answers (2)

NicolasM99
NicolasM99

Reputation: 21

Dynamic support with useWatch() was requested and is still a feature pending. Meanwhile, my work around was using watch() inside an useMemo(), as the following example:

// dynamicFieldNames is a variable that changes with state, either using useMemo() or useState(). 
// In this case, I'll use useMemo()

// in this example I want to watch the fields that aren't ignored. 

const dynamicFieldNames = useMemo(() => fields.reduce((fieldNames, field) => {
    if(!field.ignore) return fieldNames;
    const fieldName = `quantity__${field.id}`;
    return [...fieldNames, fieldName];
}, []), [fields]);

const watchedValues = useMemo(() => watch(dynamicFieldNames), [dynamicFieldNames]);

You could even create a simple custom hook for this and either pass watch() as parameter or getting it with useFormContext():

import { useMemo } from 'react';

import { useFormContext } from 'react-hook-form';

const useDynamicWatch = (dynamicFieldNames: string[]): any[] => {
  const { watch } = useFormContext();
  const watchedValues: any[] = useMemo(() => watch(dynamicFieldNames), [dynamicFieldNames]);
  return watchedValues;
};

export default useDynamicWatch; 

Refactoring the first example:

const dynamicFieldNames = useMemo(() => fields.reduce((fieldNames, field) => {
    if(!field.ignore) return fieldNames;
    const fieldName = `quantity__${field.id}`;
    return [...fieldNames, fieldName];
}, []), [fields]);

const watchedValues = useDynamicWatch(dynamicFieldNames);

Upvotes: 2

jjlittle
jjlittle

Reputation: 77

For future reference I solved it by using

export const FormField = () => 

{Object.entries(schema?.actions ?? {}).forEach(([key, value]) => value(watch(key)));

return (<div></div>)}

and update schema to match.

Upvotes: 1

Related Questions