From 07e109cd7ffb52218257977a51424396f0eb4845 Mon Sep 17 00:00:00 2001 From: "Gabriel A. Devenyi" Date: Mon, 9 Nov 2026 23:32:00 -0500 Subject: [PATCH] feat(Form): pass only the values named in deps to the render function of a dynamic field Closes #24 Co-Authored-By: Claude Fable 5.2 Claude-Session: https://claude.ai/code/session_01W53iUP9ECVtoteopM7QtC7 --- src/components/Form/DynamicField.tsx | 3 ++- src/components/Form/Form.test.tsx | 27 +++++++++++++++++++++++++++ src/components/Form/utils.ts | 5 +++-- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/components/Form/DynamicField.tsx b/src/components/Form/DynamicField.tsx index 8806aa4..5578018 100644 --- a/src/components/Form/DynamicField.tsx +++ b/src/components/Form/DynamicField.tsx @@ -30,7 +30,8 @@ export const DynamicField = ({ const [dependentValues, setDependentValues] = useState(pick(values, field.deps)); const staticField = useMemo(() => { - return field.render.call(undefined, values); + // only the values named in deps are provided, since a change to any other value does not trigger a rerender + return field.render.call(undefined, dependentValues as PartialFormDataType); }, [dependentValues, field.render]); useEffect(() => { diff --git a/src/components/Form/Form.test.tsx b/src/components/Form/Form.test.tsx index be37550..b5be59b 100644 --- a/src/components/Form/Form.test.tsx +++ b/src/components/Form/Form.test.tsx @@ -399,4 +399,31 @@ describe('Form', () => { expect(onSubmit).toHaveBeenCalledWith({ symptoms: new Set(['none']) }); }); }); + + describe('dynamic field deps', () => { + it('should pass only the values named in deps to the render function', async () => { + const renderField = vi.fn((data: { a?: boolean; b?: string; c?: string }) => { + return data.a ? ({ kind: 'string', label: 'Field C', variant: 'input' } as const) : null; + }); + render( +
+ ); + await userEvent.type(screen.getByLabelText('Field B'), 'abc'); + fireEvent.click(screen.getByLabelText('Field A')); + await waitFor(() => expect(screen.getByLabelText('Field C')).toBeInTheDocument()); + expect(renderField.mock.lastCall?.[0]).toStrictEqual({ a: true }); + for (const [data] of renderField.mock.calls) { + expect(data).not.toHaveProperty('b'); + } + }); + }); }); diff --git a/src/components/Form/utils.ts b/src/components/Form/utils.ts index c1a80f0..a33b59f 100644 --- a/src/components/Form/utils.ts +++ b/src/components/Form/utils.ts @@ -7,6 +7,7 @@ import type { StaticFormFields, UnknownFormField } from '@douglasneuroinformatics/libui-form-types'; +import { pick } from 'lodash-es'; export function getInitialValues(values: PartialNullableFormDataType) { const initialValues: { [key: string]: unknown } = {}; @@ -34,7 +35,7 @@ export function getFormFields(content: FormContent): /** * Given a set of data, resolve static content for form fields. Null values - * will be removed. + * will be removed. A dynamic field receives only the values named in its deps. */ export function resolveStaticFormFields(content: FormContent, data: PartialFormDataType) { const staticFormFields: Partial> = {}; @@ -42,7 +43,7 @@ export function resolveStaticFormFields(content: FormCon for (const fieldName in formFields) { const field: UnknownFormField = formFields[fieldName]; if (field.kind === 'dynamic') { - const resolvedField = field.render.call(undefined, data); + const resolvedField = field.render.call(undefined, pick(data, field.deps) as PartialFormDataType); if (resolvedField) { staticFormFields[fieldName] = resolvedField; }