Skip to main content

Getting started

This walkthrough builds a minimal name + tags form.

1. Define field refs

types.ts
import { defineField, type FieldRef } from '@ekz/formix';
import { Set } from 'immutable';

export type ProfileFieldRefs = {
name: FieldRef<string>;
tags: FieldRef<Set<string>>;
};

export function defineProfileFields(initial?: {
name?: string;
tags?: Set<string>;
}): ProfileFieldRefs {
return {
name: defineField(initial?.name ?? ''),
tags: defineField(initial?.tags ?? Set<string>()),
};
}

export type ProfilePayload = {
name: string;
tags: string[];
};

export function getProfileValues(
refs: ProfileFieldRefs,
getValues: (ref: FieldRef<unknown>) => unknown,
): ProfilePayload {
return {
name: getValues(refs.name) as string,
tags: (getValues(refs.tags) as Set<string>).toArray(),
};
}

2. Wrap the form

ProfileForm.tsx
import React from 'react';
import { useForm, withForm } from '@ekz/formix';
import { defineProfileFields, getProfileValues } from './types';

type Props = {
onSave: (payload: ReturnType<typeof getProfileValues>) => void;
};

function ProfileForm({ onSave }: Props) {
const fieldRefs = React.useMemo(() => defineProfileFields(), []);
const { getValues, modified, valid } = useForm();

const handleSave = React.useCallback(() => {
onSave(getProfileValues(fieldRefs, getValues));
}, [fieldRefs, getValues, onSave]);

return (
<form
onSubmit={(event) => {
event.preventDefault();
handleSave();
}}
>
<NameInput field={fieldRefs.name} />
<button type="submit" disabled={!modified || !valid}>
Save
</button>
</form>
);
}

export const ProfileFormWithContext = withForm(ProfileForm);

3. Bind an input

NameInput.tsx
import React from 'react';
import { useField, useFieldValidation, type FieldRef } from '@ekz/formix';

const REQUIRED = 'Name is required.';

export function NameInput({ field }: { field: FieldRef<string> }) {
const { value, setValue, error, touched } = useField(field);

useFieldValidation(
field,
React.useCallback((name) => (name.trim() === '' ? REQUIRED : null), []),
);

return (
<label>
Name
<input
value={value}
onChange={(event) => setValue(event.target.value)}
/>
{touched && error != null ? <span role="alert">{error}</span> : null}
</label>
);
}

4. Render

<ProfileFormWithContext onSave={(payload) => console.log(payload)} />

What you get

  • modifiedtrue after the user changes a field
  • validfalse while any registered validator returns an error
  • getValues — read current values through field refs for serialization

Continue with the form shell guide and validation guide for modals, array fields, and cross-field rules.