import {useMemo} from 'react'; import {useFormContext} from 'react-hook-form'; import {FieldCondition} from '@givewp/forms/types'; import conditionOperatorFunctions from '@givewp/forms/app/utilities/conditionOperatorFunctions'; type WatchedFields = Map; /** * Adds visibility conditions to a field. The given conditions are watched and the hook returns true or false based on * whether the conditions are met. * * 4.14.1 use watch instead of useWatch to get the field values. * @since 3.0.0 */ export default function useVisibilityCondition(conditions: FieldCondition[]): boolean { const {watch} = useFormContext(); const watchedFieldNames = useMemo(() => { if (!conditions.length) { return []; } return [...conditions.reduce(watchFieldsReducer, new Set()).values()]; }, [conditions]); const fieldValues = watch(watchedFieldNames); // watch returns a numeric array of values, so we need to map them to the field names. const watchedFields = useMemo(() => { return watchedFieldNames.reduce((fields, name, index) => { fields.set(name, fieldValues[index]); return fields; }, new Map()); }, [watchedFieldNames, fieldValues]); return useMemo(() => { if (!conditions.length) { return true; } return visibliityConditionsPass(conditions, watchedFields); }, [watchedFields]); } /** * Returns true or false based on whether the conditions are met. * * @since 3.0.0 */ export function visibliityConditionsPass(conditions: FieldCondition[], watchedFields: WatchedFields): boolean { if (!conditions.length) { return true; } function conditionPassReducer(passing: boolean, condition: FieldCondition) { if (condition.type === 'basic') { const value = watchedFields.get(condition.field); const conditionPasses = conditionOperatorFunctions[condition.comparisonOperator](value, condition.value); return condition.logicalOperator === 'and' ? (passing ?? true) && conditionPasses : (passing ?? false) || conditionPasses; } return condition.boolean === 'and' ? (passing ?? true) && condition.conditions.reduce(conditionPassReducer, null) : (passing ?? false) || condition.conditions.reduce(conditionPassReducer, null); } return conditions.reduce(conditionPassReducer, null); } /** * A recursive reducer that returns a set of fields that are being watched by the conditions. * * @since 3.0.0 */ function watchFieldsReducer(fields: Set, condition: FieldCondition) { if (condition.type === 'basic') { return fields.add(condition.field); } return condition.conditions.reduce(watchFieldsReducer, fields); }