Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling Record<string, string> Input with Superforms #447

Open
mpost opened this issue Jun 26, 2024 · 3 comments
Open

Handling Record<string, string> Input with Superforms #447

mpost opened this issue Jun 26, 2024 · 3 comments
Milestone

Comments

@mpost
Copy link

mpost commented Jun 26, 2024

Description:

We are using a Zod schema with Record<string, string> to support a multi-language message. Our schema is defined as follows:

z.object({
  message: z.record(z.string()).optional(),
});

This allows inputs such as:

{
  message: {
    de: "guten tag",
    en: "good day",
    fr: "bonjour",
    // ...
  }
}

Our goal is to build a custom component that accepts the message key as input and displays the various messages within a single component. While this works fine for single values (e.g., message: "hello"), we are unable to correctly pass the Record based message to a component.

Current Implementation:

Here is our current code, which does not have the correct type for the field value. Typically, you would use FormPathLeaves<T> directly, but that is not possible with Record. Additionally, the code has incorrect types for the $errors and $value stores, as it treats them as singular values.

<script lang="ts" context="module">
  type T = Record<string, unknown>;
</script>

<script lang="ts" generics="T extends Record<string, unknown>">
  import MultiLanguageTextarea from '$lib/components/form/MultiLanguageTextarea.svelte';
  import { formFieldProxy, type SuperForm, type FormPathLeaves } from 'sveltekit-superforms';
  import type { Writable } from 'svelte/store';

  export let form: SuperForm<T>;
  export let field: string;

  $: leaves = field as FormPathLeaves<T>
  $: ({ value, errors } = formFieldProxy(form, leaves))
</script>

<MultiLanguageTextarea bind:value={$value} errors={$errors} {...$$restProps} />

Question:

What is the correct way to handle a Record<string, string> based input when using Superforms?

Expected Behavior:

We need a solution that correctly types the field value and ensures the $errors and $value stores are correctly typed as Record<string, string>.

Any guidance or examples on how to achieve this would be greatly appreciated. Thank you!

@mpost mpost added the bug Something isn't working label Jun 26, 2024
@ciscoheat ciscoheat removed the bug Something isn't working label Jul 3, 2024
@ciscoheat
Copy link
Owner

What is the error you're getting? This works very well for me:

<script lang="ts" context="module">
	type T = Record<string, unknown>;
</script>

<script lang="ts" generics="T extends Record<string, unknown>">
	import { formFieldProxy, type SuperForm, type FormPathLeaves } from '$lib/index.js';

	export let form: SuperForm<T>;
	export let field: FormPathLeaves<T>;

	$: ({ value, errors } = formFieldProxy(form, field));
</script>

<label>
	{field}: <input name={field} bind:value={$value} aria-invalid={$errors ? 'true' : undefined} />
	{#if $errors}<span class="invalid">{$errors}</span>{/if}
</label>
<script lang="ts">
  const superform = superForm(data.form, {
    taintedMessage: false,
    dataType: 'json'
  });
</script>

<RecordField field="message.name" form={superform} />

@mpost
Copy link
Author

mpost commented Jul 29, 2024

Thanks for getting back in the issue. In your example you bind to the RecordField

<RecordField field="message.name" form={superform} />

which likely implies that message.name represents a singular value. In my example we want to work with nested data structure and pass that down to the custom component. Eg

{
  message: {
    de: "guten tag",
    en: "good day",
    fr: "bonjour",
    // ...
  }
}

We would want to bind to the data.message field, which should pass down the Record of languages to the underlying <MultiLanguageTextArea>.

@ciscoheat ciscoheat added this to the v3 milestone Oct 28, 2024
@tijptjik
Copy link

Just tacking on another example here for what I'd hope would one day be supported:

I'm interested in accessing the constraints for a nested objects where the keys are unknown at compile time.

export const CustomProperty = z.object({
  type: z.enum(customPropertyTypes),
  key: z.string().min(3, { message: 'Key should have at least 3 characters' }),
  ...
})

export const CustomPropertySchema = z.object({
  classifiers: z.record(z.string(), CustomProperty),
  specifiers: z.record(z.string(), CustomProperty),
});

export let ProjectInsert = createInsertSchema(project, {
  metadata: CustomPropertySchema
});

The constraints object returned from running superValidate() with zod(ProjectInsert) - i.e. form = await superValidate(formData, zod(ProjectInsert)); with the following constraints:

metadata:
​​    classifiers: 
​​        required: true
    specifiers :
​​        required: true

The more deeply nested constraints are not available -- I've confirmed with @ciscoheat that this currently cannot be achieved.

Note that validation DOES take the nested rules into consideration, and the errors show up in the right path. However, it would also be great to have access to the constraints of nested fields.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants