Skip to main content

📝 Form Validation Example

This example demonstrates how to manage form validation using Retomus.
We'll track field values and validation state, and control form submission based on validity.


Status: idle


🔧 Machine Definition

import { createRetomus, createMachineConfig } from 'retomus';

const retomus = createRetomus();

const formMachine = retomus.createMachine(
createMachineConfig({
id: 'form',
status: ['idle', 'valid', 'invalid', 'submitted'],
actions: ['update', 'submit', 'reset'],
transitions: {
idle: {
update: ['valid', 'invalid'],
reset: 'idle',
},
valid: {
reset: 'idle',
submit: 'submitted',
update: ['valid', 'invalid'],
},
invalid: {
update: ['valid', 'invalid'],
reset: 'idle',
},
submitted: {
reset: 'idle',
},
},
router: {
update: ctx => (ctx.state.isValid ? 'valid' : 'invalid'),
},
actionHandlers: {
update: ({ ctx, payload, done }) => {
const { value, field } = payload;
ctx.state[field] = value;
console.log('update action', payload, ctx.state.name, ctx.state.email);
ctx.state.isValid =
ctx.state.name.length > 0 && ctx.state.email.includes('@');
done();
},
submit: ({ done }) => done(),
reset: ({ ctx, done }) => {
ctx.state.name = '';
ctx.state.email = '';
ctx.state.isValid = false;
done();
},
},
initialStatus: { status: 'idle' },
ctx: {
state: {
name: '',
email: '',
isValid: false,
},
},
}),
);

🧩 React Component

import React, { CSSProperties } from 'react';

const FormComponent = ({ formMachine }) => {
const name = formMachine.useState('name');
const email = formMachine.useState('email');
const isValid = formMachine.useState('isValid');
const status = formMachine.useStatus();

const update = formMachine.useAction('update');
const submit = formMachine.useAction('submit');
const reset = formMachine.useAction('reset');

const handleInputChange =
(field: 'name' | 'email') => (e: React.ChangeEvent<HTMLInputElement>) => {
update({ value: e.target.value, field });
};

return (
<form
onSubmit={e => {
e.preventDefault();
submit();
}}
style={styles.form}
>
<h2>Status: {status}</h2>

<div style={styles.fieldGroup}>
<input
type='text'
placeholder='Name'
value={name}
onChange={handleInputChange('name')}
style={styles.input}
/>
<input
type='email'
placeholder='Email'
value={email}
onChange={handleInputChange('email')}
style={styles.input}
/>
</div>

<div style={styles.buttonGroup}>
<button type='submit' disabled={!isValid}>
Submit
</button>
<button type='button' onClick={reset}>
Reset
</button>
</div>
</form>
);
};

const styles: {
form: CSSProperties;
fieldGroup: CSSProperties;
input: CSSProperties;
buttonGroup: CSSProperties;
} = {
form: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: '1rem',
},
fieldGroup: {
display: 'flex',
flexDirection: 'column',
gap: '1rem',
margin: '1rem 0',
},
input: {
backgroundColor: 'black',
padding: '0.25rem',
color: 'white',
},
buttonGroup: {
display: 'flex',
justifyContent: 'center',
gap: '1rem',
},
};

💡 Concepts

✅ Real-time field updates
✅ Derived validation status
✅ Controlled transitions based on context