Skip to content

Commit

Permalink
feat: update field value with useEffect
Browse files Browse the repository at this point in the history
  • Loading branch information
edmundhung committed Aug 3, 2024
1 parent 6b98c07 commit 3830c59
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 42 deletions.
32 changes: 15 additions & 17 deletions packages/conform-dom/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,7 @@ function createFormMeta<Schema, FormError, FormValue>(
value: initialValue,
constraint: options.constraint ?? {},
validated: lastResult?.state?.validated ?? {},
key: !initialized
? getDefaultKey(defaultValue)
: {
'': generateId(),
...getDefaultKey(defaultValue),
},
key: getDefaultKey(defaultValue),
// The `lastResult` should comes from the server which we won't expect the error to be null
// We can consider adding a warning if it happens
error: (lastResult?.error as Record<string, FormError>) ?? {},
Expand All @@ -301,15 +296,20 @@ function getDefaultKey(
): Record<string, string> {
return Object.entries(flatten(defaultValue, { prefix })).reduce<
Record<string, string>
>((result, [key, value]) => {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
result[formatName(key, i)] = generateId();
>(
(result, [key, value]) => {
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
result[formatName(key, i)] = generateId();
}
}
}

return result;
}, {});
return result;
},
{
[prefix ?? '']: generateId(),
},
);
}

function setFieldsValidated<Error>(
Expand Down Expand Up @@ -441,10 +441,8 @@ function updateValue<Error>(
if (name === '') {
meta.initialValue = value as Record<string, unknown>;
meta.value = value as Record<string, unknown>;
meta.key = {
...getDefaultKey(value as Record<string, unknown>),
'': generateId(),
};
meta.key = getDefaultKey(value as Record<string, unknown>);

return;
}

Expand Down
70 changes: 68 additions & 2 deletions packages/conform-react/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
useContext,
useSyncExternalStore,
useRef,
useEffect,
} from 'react';

export type Pretty<T> = { [K in keyof T]: T[K] } & {};
Expand Down Expand Up @@ -152,7 +153,67 @@ export function useFormState<FormError>(
[form, subjectRef],
);

return useSyncExternalStore(subscribe, form.getState, form.getState);
const state = useSyncExternalStore(subscribe, form.getState, form.getState);

useEffect(() => {
const formId = form.getFormId();
const formElement = document.forms.namedItem(formId);
const scope = subjectRef?.current.key;

if (!formElement || !scope) {
return;
}

const getAll = (value: unknown) => {
if (typeof value === 'string') {
return [value];
}

if (
Array.isArray(value) &&
value.every((item) => typeof item === 'string')
) {
return value;
}

return undefined;
};
const get = (value: unknown) => getAll(value)?.[0];

for (const element of formElement.elements) {
if (
(element instanceof HTMLInputElement ||
element instanceof HTMLTextAreaElement ||
element instanceof HTMLSelectElement) &&
scope.name?.includes(element.name)
) {
const prev = element.dataset.conform;
const next = state.key[element.name];
const defaultValue = state.initialValue[element.name];

if (typeof prev === 'undefined' || prev !== next) {
element.dataset.conform = next;

if ('options' in element) {
const value = getAll(defaultValue) ?? [];

for (const option of element.options) {
option.selected = value.includes(option.value);
}
} else if (
'checked' in element &&
(element.type === 'checkbox' || element.type === 'radio')
) {
element.checked = get(defaultValue) === element.value;
} else {
element.value = get(defaultValue) ?? '';
}
}
}
}
}, [form, state, subjectRef]);

return state;
}

export function FormProvider(props: {
Expand Down Expand Up @@ -307,9 +368,14 @@ export function getMetadata<
case 'initialValue':
case 'value':
case 'valid':
case 'dirty':
case 'dirty': {
if (key === 'initialValue') {
// If `initialValue` is subscribed, it's likely that we will want the field to be updated
updateSubjectRef(subjectRef, 'key', 'name', name);
}
updateSubjectRef(subjectRef, key, 'name', name);
break;
}
case 'errors':
case 'allErrors':
updateSubjectRef(
Expand Down
3 changes: 1 addition & 2 deletions packages/conform-react/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FormMetadata, FieldMetadata, Metadata, Pretty } from './context';

type FormControlProps = {
key: string | undefined;
key?: string;
id: string;
name: string;
form: string;
Expand Down Expand Up @@ -214,7 +214,6 @@ export function getFormControlProps<Schema>(
options?: FormControlOptions,
): FormControlProps {
return simplify({
key: metadata.key,
required: metadata.required || undefined,
...getFieldsetProps(metadata, options),
});
Expand Down
24 changes: 3 additions & 21 deletions packages/conform-react/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export function getFieldElements(
const elements = !field
? []
: field instanceof Element
? [field]
: Array.from(field.values());
? [field]
: Array.from(field.values());

return elements.filter(
(
Expand Down Expand Up @@ -306,26 +306,8 @@ export function useControl<
change(value);
};

const refCallback: RefCallback<
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | undefined
> = (element) => {
register(element);

if (!element) {
return;
}

const prevKey = element.dataset.conform;
const nextKey = `${meta.key ?? ''}`;

if (prevKey !== nextKey) {
element.dataset.conform = nextKey;
updateFieldValue(element, value ?? '');
}
};

return {
register: refCallback,
register,
value,
change: handleChange,
focus,
Expand Down

0 comments on commit 3830c59

Please sign in to comment.