Recipe: Handling Nested Data
Relevant source files
The following files were used as context for generating this wiki page:The @opensky/remotes package provides utilities to enhance SvelteKit's remote form functions, with a particular focus on improving the validation experience for complex forms. A key feature is its ability to seamlessly handle validation for fields within nested data structures. This is achieved by using a simple dot-notation string path to reference and manage validation issues for individual fields, regardless of their depth in the form's data object.
This recipe details the architecture and usage patterns for managing nested data validation using the createValidation helper. It covers how to reference nested fields, how the internal state is managed, and how the helper functions interact with SvelteKit's RemoteForm to provide a reactive and user-friendly validation system.
Sources: README.md, packages/remotes/src/lib/validation.svelte.ts
Core Concept: Dot-Notation Paths
The system identifies nested fields using a string path where each level of nesting is separated by a dot. For example, a field state inside an address object in your form data would be referenced by the path 'address.state'. This approach simplifies the process of binding validation logic and displaying issues for deeply nested form inputs.
Sources: README.md:26-27, packages/remotes/src/lib/validation.svelte.ts:21-22
The following diagram illustrates how a dot-notation path maps to a property in a nested JSON-like structure.
The createValidation Helper
The primary utility for this recipe is createValidation, which is exported from the package. It takes a SvelteKit RemoteForm object as its argument and returns a set of methods for managing validation state.
Sources: packages/remotes/src/lib/index.ts:1, packages/remotes/src/lib/validation.svelte.ts:12-16
Returned Methods
The createValidation function returns an object with several methods to interact with the validation state.
| Method | Description |
|---|---|
fields(path) | Returns onblur and oninput event handlers for a given field path. Registers the path for validation tracking. |
issues(path) | Returns an array of validation issue strings for a field path, or null if there are no issues. |
allIssues() | Returns the entire validation issues object. |
addIssue(path, issue) | Adds a custom, client-side validation error message to a specific field path. |
reset() | Clears all validation issues from the state. |
validateAll() | Triggers validation for all registered fields against the server. |
updateIssues() | Populates the local validation state from the RemoteForm's issues, typically after a server round-trip. |
Sources: README.md:15-23, packages/remotes/src/lib/validation.svelte.ts:221-230
Internal Mechanics of Nested State Management
At its core, createValidation maintains a reactive state object called issues. This object mirrors the structure of the form data, holding arrays of error messages for each field. The helper functions setNestedValue and getNestedValue are responsible for interacting with this state object using the dot-notation paths.
Sources: packages/remotes/src/lib/validation.svelte.ts:7-8
setNestedValue Logic
When a validation issue needs to be added or cleared for a field like 'address.state', the setNestedValue function is called internally. It parses the path, traverses the issues state object, and creates nested objects as needed before setting the value at the final destination.
The following flowchart shows the internal logic of the setNestedValue function.
getNestedValue Logic
Conversely, getNestedValue is used by the issues(path) method. It traverses the issues state object according to the provided path to retrieve the corresponding array of error messages. If no issues are found or the path is invalid, it returns null.
// packages/remotes/src/lib/validation.svelte.ts:47-55
function getNestedValue(path: string): string[] | null {
const keys = path.split('.')
const fieldIssues = keys.reduce((current, key) => current?.[key], issues)
if (fieldIssues && Array.isArray(fieldIssues) && fieldIssues.length > 0) {
return fieldIssues
}
return null
}
Sources: packages/remotes/src/lib/validation.svelte.ts:47-55
Field Interaction and Validation Flow
The fields(path) method is the primary way to connect a form input to the validation system. It returns event handlers that trigger validation logic.
oninput: When the user types, this handler checks if the field currently has issues. If so, it triggers apreflightOnlyvalidation to clear the issues as the user corrects the input. This provides immediate feedback without hitting the server on every keystroke.onblur: When the user leaves a field, this handler triggers a debounced remote validation. It first attempts a preflight validation and then a full server validation if no client-side issues are found. This ensures server-side rules are checked without overwhelming the server.
The following sequence diagram illustrates the onblur validation flow for a nested field.
Integration with createEnhancedForm
For a more streamlined experience, the createValidation instance can be passed to createEnhancedForm. This automates the process of updating validation state after a form submission.
When a form submission returns with validation errors, createEnhancedForm automatically calls validation.updateIssues(). If the submission fails with an exception (e.g., network error), it calls validation.validateAll() to re-synchronize the validation state. This integration removes the need for manual calls in the form enhancement callbacks.
Sources: README.md:112-116, packages/remotes/src/lib/enhance.svelte.ts:299-300, 307-308
<script lang="ts">
import { createValidation, createEnhancedForm } from '@opensky/remotes';
import { myForm } from './myForm.remote';
import { z } from 'zod';
const schema = z.object({
address: z.object({
state: z.string()
})
});
// 1. Create validation instance
const valid = createValidation(myForm);
// 2. Pass instance to createEnhancedForm
const form = createEnhancedForm(myForm, {
validation: valid
});
</script>
<!-- 3. Bind fields using dot-notation -->
<input
{...myForm.fields.address.state.as('text')}
{...valid.fields('address.state')}
class:error={valid.issues('address.state')}
/>
Sources: README.md:162-177, 194-196
Conclusion
The @opensky/remotes package provides a robust and developer-friendly solution for handling validation in forms with nested data. By abstracting the complexity of traversing and managing a nested state object behind a simple dot-notation API, the createValidation helper allows developers to implement sophisticated, reactive validation UX with minimal boilerplate. The seamless integration with createEnhancedForm further simplifies the development process by automating state updates during the form submission lifecycle.
Sources: README.md:3-5, packages/remotes/package.json:3