Reputation: 77
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
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
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