Skip to content

Commit

Permalink
fix: dataType: 'json' nested field errors (#159)
Browse files Browse the repository at this point in the history
Co-authored-by: AdrianGonz97 <[email protected]>
  • Loading branch information
huntabyte and AdrianGonz97 committed Mar 27, 2024
1 parent 01e10cb commit a5966c1
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/nervous-mayflies-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"formsnap": patch
---

Fixed a bug where `FieldErrors` wouldn't render errors for nested fields with `dataType: 'json'`
1 change: 1 addition & 0 deletions packages/formsnap/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_modules
/build
/.svelte-kit
/package
/dist
.env
.env.*
!.env.example
Expand Down
6 changes: 6 additions & 0 deletions packages/formsnap/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ module.exports = {
}
]
}
},
{
files: ['*.ts', '*.js', '*.svelte'],
rules: {
'no-console': 'error'
}
}
]
};
21 changes: 11 additions & 10 deletions packages/formsnap/src/lib/components/field.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
</script>

<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
import { getValueAtPath } from '$lib/internal/utils/path.js';
import type { FieldProps } from './types.js';
import { setFormField, type FieldContext } from '$lib/context.js';
Expand All @@ -23,26 +25,25 @@
errors: formErrors,
constraints: formConstraints,
tainted: formTainted,
form: formData,
isTainted
form: formData
} = form);
const field: FieldContext<T, U> = {
name: writable<U>(name),
errors: writable<string[]>([]),
constraints: writable<Record<string, unknown>>({}),
name: writable(name),
errors: writable([]),
constraints: writable({}),
tainted: writable(false),
fieldErrorsId: writable<string>(),
descriptionId: writable<string>(),
fieldErrorsId: writable(),
descriptionId: writable(),
form
};
const { tainted, errors } = field;
$: field.name.set(name);
$: field.errors.set(extractErrorArray($formErrors[name]));
$: field.constraints.set($formConstraints[name] ?? {});
$: field.tainted.set($formTainted ? isTainted($formTainted[name]) : false);
$: field.errors.set(extractErrorArray(getValueAtPath(name, $formErrors)));
$: field.constraints.set(getValueAtPath(name, $formConstraints) ?? {});
$: field.tainted.set($formTainted ? getValueAtPath(name, $formTainted) === true : false);
setFormField<T, U>(field);
</script>
Expand Down
2 changes: 1 addition & 1 deletion packages/formsnap/src/lib/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function getFormControl(): FormControlContext {
}

function ctxError(ctx: string) {
console.error(
throw new Error(
`Unable to find \`${ctx}\` context. Did you forget to wrap the component in a \`${ctx}\`?`
);
}
1 change: 0 additions & 1 deletion packages/formsnap/src/lib/internal/utils/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export function extractErrorArray<T extends Record<string, unknown>>(
errors: ValidationErrors<T> | undefined
): string[] {
if (Array.isArray(errors)) return errors;

if (typeof errors === 'object' && '_errors' in errors) {
if (errors._errors !== undefined) return errors._errors;
}
Expand Down
5 changes: 2 additions & 3 deletions packages/formsnap/src/lib/internal/utils/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ export function getValueAtPath(path: string, obj: Record<string, unknown>) {
let value = obj as any;

for (const key of keys) {
if (typeof value !== 'object') {
if (typeof value !== 'object' || value === null) {
return undefined; // Handle cases where the path doesn't exist in the object
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value = value[key] as any;
value = value[key];
}

return value;
Expand Down
46 changes: 46 additions & 0 deletions sites/docs/src/routes/docs/examples/json/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script lang="ts" context="module">
import { z } from "zod";
export const formSchema = z.object({
name: z.string(),
address: z.object({
country: z.string(),
state: z.string(),
city: z.string(),
streetName: z.string(),
streetNumber: z.string(),
building: z.string(),
apartment: z.string(),
postcode: z.string().min(4).max(10),
}),
});
export type FormSchema = typeof formSchema;
</script>

<script lang="ts">
import SuperDebug, { defaults, superForm } from "sveltekit-superforms";
import { zod } from "sveltekit-superforms/adapters";
import { Field, FieldErrors, Control, Label } from "formsnap";
const form = superForm(defaults(zod(formSchema)), {
SPA: true,
resetForm: false,
validators: zod(formSchema),
dataType: "json",
});
const { form: formData, enhance, errors } = form;
</script>

<SuperDebug data={$errors} />
<form method="POST" use:enhance class="flex flex-col gap-2">
<Field {form} name="address.postcode">
<Control let:attrs>
<Label>Postcode</Label>
<input {...attrs} bind:value={$formData.address.postcode} />
</Control>
<FieldErrors class="text-red-500" />
</Field>

<button class="mt-6">Submit</button>
</form>

0 comments on commit a5966c1

Please sign in to comment.