Skip to content

Commit fa1a4f9

Browse files
authored
Merge pull request #71 from DouglasNeuroInformatics/dynamic-field-change
feat: add new subscribe option for form
2 parents 06faf57 + 1521cc4 commit fa1a4f9

File tree

2 files changed

+98
-2
lines changed

2 files changed

+98
-2
lines changed

src/components/Form/Form.stories.tsx

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/* eslint-disable perfectionist/sort-objects */
22

3-
import { useEffect, useState } from 'react';
3+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
44

5-
import { sleep } from '@douglasneuroinformatics/libjs';
5+
import { filterObject, sleep } from '@douglasneuroinformatics/libjs';
66
import type { ZodTypeLike } from '@douglasneuroinformatics/libjs';
77
import type { FormFields } from '@douglasneuroinformatics/libui-form-types';
88
import type FormTypes from '@douglasneuroinformatics/libui-form-types';
@@ -535,6 +535,86 @@ export const WithSuspend: StoryObj<typeof Form<ZodTypeLike<FormTypes.Data>, { de
535535
}
536536
};
537537

538+
export const WithSubscribe: StoryObj<
539+
typeof Form<ZodTypeLike<FormTypes.Data>, { options: string; searchTerm: string }>
540+
> = {
541+
decorators: [
542+
(Story) => {
543+
const defaultOptions = useMemo<{ [key: string]: string }>(
544+
() => ({
545+
a: 'Option A',
546+
b: 'Option B',
547+
c: 'Option C',
548+
d: 'Option D'
549+
}),
550+
[]
551+
);
552+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
553+
const [options, setOptions] = useState<typeof defaultOptions>(defaultOptions);
554+
555+
const handleChange = useCallback(
556+
(
557+
values: Partial<{ options: string; searchTerm: string }>,
558+
setValues: React.Dispatch<React.SetStateAction<Partial<{ options: string; searchTerm: string }>>>
559+
) => {
560+
clearTimeout(timeoutRef.current);
561+
const lowerCaseSearch = values.searchTerm?.toLowerCase();
562+
if (!lowerCaseSearch) {
563+
setOptions(defaultOptions);
564+
return;
565+
}
566+
timeoutRef.current = setTimeout(() => {
567+
const updatedOptions = filterObject(defaultOptions, (value) =>
568+
value.toLowerCase().includes(lowerCaseSearch)
569+
) as {
570+
[key: string]: string;
571+
};
572+
setOptions(updatedOptions);
573+
if (values.options !== undefined && !Object.keys(updatedOptions).includes(values.options)) {
574+
setValues((prevValues) => ({
575+
...prevValues,
576+
options: undefined
577+
}));
578+
}
579+
}, 500);
580+
},
581+
[]
582+
);
583+
584+
return (
585+
<Story
586+
args={{
587+
content: {
588+
searchTerm: {
589+
kind: 'string',
590+
label: 'Search Term',
591+
variant: 'input'
592+
},
593+
options: {
594+
kind: 'string',
595+
label: 'Options',
596+
options,
597+
variant: 'select'
598+
}
599+
},
600+
subscribe: {
601+
onChange: handleChange,
602+
selector: (values) => values.searchTerm
603+
},
604+
onSubmit: (data) => {
605+
alert(JSON.stringify(data, (_key, value) => (value instanceof Set ? [...value] : (value as unknown)), 2));
606+
},
607+
validationSchema: z.object({
608+
searchTerm: z.string().min(1),
609+
options: z.enum(['a', 'b', 'c', 'd'])
610+
})
611+
}}
612+
/>
613+
);
614+
}
615+
]
616+
};
617+
538618
export const WithError: StoryObj<typeof Form> = {
539619
args: {
540620
content: {

src/components/Form/Form.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,18 @@ type FormProps<TSchema extends ZodTypeLike<FormDataType>, TData extends TSchema[
4747
onError?: (error: ZodErrorLike) => void;
4848
onSubmit: FormSubmitHandler<NoInfer<TData>>;
4949
preventResetValuesOnReset?: boolean;
50+
5051
readOnly?: boolean;
5152
resetBtn?: boolean;
5253
revalidateOnBlur?: boolean;
5354
submitBtnLabel?: string;
55+
subscribe?: {
56+
onChange: (
57+
values: PartialFormDataType<TData>,
58+
setValues: React.Dispatch<React.SetStateAction<PartialFormDataType<TData>>>
59+
) => Promisable<void>;
60+
selector: (values: PartialFormDataType<TData>) => unknown;
61+
};
5462
suspendWhileSubmitting?: boolean;
5563
validationSchema: ZodTypeLike<TData>;
5664
};
@@ -71,6 +79,7 @@ const Form = <TSchema extends ZodTypeLike<FormDataType>, TData extends TSchema['
7179
resetBtn,
7280
revalidateOnBlur,
7381
submitBtnLabel,
82+
subscribe,
7483
suspendWhileSubmitting,
7584
validationSchema,
7685
...props
@@ -105,6 +114,13 @@ const Form = <TSchema extends ZodTypeLike<FormDataType>, TData extends TSchema['
105114
}
106115
};
107116

117+
useEffect(() => {
118+
if (!subscribe) {
119+
return;
120+
}
121+
subscribe.onChange(values, setValues);
122+
}, [subscribe?.selector(values)]);
123+
108124
const reset = () => {
109125
setRootErrors([]);
110126
setErrors({});

0 commit comments

Comments
 (0)