Houd me bezig met het implementeren van een Svelte-toestandsmachine voor complexe UI-interacties
description
Door het implementeren van een toestandsmachine kan ik de betrouwbaarheid en onderhoudbaarheid van de UI van mijn toepassing verbeteren, wat leidt tot een soepelere gebruikerservaring en eenvoudiger debuggen.
prompt
try_prompt
Help me bij het opzetten van een toestandsmachine in mijn Svelte-toepassing om complexe UI-interacties en overgangen te beheren. Beschrijf hoe toestanden, gebeurtenissen en overgangen te definiëren, terw ... more
generate_helper
...
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Zeker! Het opzetten van een toestandsmachine in je Svelte-toepassing helpt om complexe UI-interacties en overgangen overzichtelijk en onderhoudbaar te houden. Hier is een gestructureerde aanpak:
1. Gebruik een state machine bibliotheek
Voor Svelte is bijvoorbeeld [XState](https://xstate.js.org/) zeer geschikt. Het biedt een declaratieve manier om toestanden, gebeurtenissen en overgangen te definiëren.
2. Definieer je toestanden (states)
Voor je registratieproces met e-mailverificatie kun je bijvoorbeeld de volgende toestanden hebben:
- `idle` (wachten op actie)
- `registering` (gebruiker voert registratie in)
- `verificatieVerzonden` (verificatiemail verstuurd)
- `verificatieWacht` (wacht op verificatie)
- `gevalideerd` (voltooide registratie)
- `fout` (fout bij registratie of verificatie)
3. Definieer je gebeurtenissen (events)
Bijvoorbeeld:
- `START_REGISTRATIE`
- `INGAVE_INGEDRUKT` (bijvoorbeeld invoer e-mail of wachtwoord)
- `VERZEND_VERIFICATIE_MAIL`
- `VERIFICATIE_ONTVANGEN` (bijvoorbeeld door polling of WebSocket)
- `FOUT_MELDING`
- `RETRY` (opnieuw proberen)
4. Specifyer overgangen (transitions)
Voorbeeld:
- Van `idle` naar `registering` op `START_REGISTRATIE`
- Van `registering` naar `verificatieVerzonden` op `VERZEND_VERIFICATIE_MAIL`
- Van `verificatieVerzonden` naar `verificatieWacht` na succesvolle mail
- Van `verificatieWacht` naar `gevalideerd` op `VERIFICATIE_ONTVANGEN` (bijvoorbeeld via polling of event)
- Van elke toestand naar `fout` bij fouten
5. Maak een XState machine en integreer in Svelte
Gebruik de `useMachine` hook van `@xstate/svelte` voor eenvoudige integratie. Voorbeeld:
```svelte
<script>
import { createMachine } from 'xstate';
import { useMachine } from '@xstate/svelte';
const registratieMachine = createMachine({
id: 'registratie',
initial: 'idle',
states: {
idle: {
on: { START_REGISTRATIE: 'registering' }
},
registering: {
on: {
VERZEND_VERIFICATIE_MAIL: 'verificatieVerzonden',
FOUT_MELDING: 'fout'
}
},
verificatieVerzonden: {
on: {
VERIFICATIE_ONTVANGEN: 'gevalideerd',
FOUT_MELDING: 'fout'
}
},
verificatieWacht: {
on: {
VERIFICATIE_ONTVANGEN: 'gevalideerd',
FOUT_MELDING: 'fout'
}
},
gevalideerd: {
type: 'final'
},
fout: {
on: { RETRY: 'registering' }
}
}
});
const [state, send] = useMachine(registratieMachine);
</script>
<!-- UI afhankelijk van de toestand -->
{#if state.matches('idle')}
<button on:click={() => send('START_REGISTRATIE')}>Registreren</button>
{/if}
{#if state.matches('registering')}
<p>Verzenden verificatiemail...</p>
<button on:click={() => send('VERZEND_VERIFICATIE_MAIL')}>Verzend mail</button>
{/if}
{#if state.matches('verificatieVerzonden')}
<p>Verificatiemail verzonden. Wacht op verificatie...</p>
{/if}
{#if state.matches('gevalideerd')}
<p>Registratie voltooid!</p>
{/if}
{#if state.matches('fout')}
<p>Er is een fout opgetreden. Probeer opnieuw.</p>
<button on:click={() => send('RETRY')}>Opnieuw proberen</button>
{/if}
```
6. Onderhoudbaarheid en overzichtelijkheid
- Definieer je machine in een apart bestand voor hergebruik en overzicht.
- Documenteer de toestanden en gebeurtenissen.
- Gebruik duidelijke, beschrijvende naamgeving.
- Overweeg het gebruik van functies of condities om complexe overgangen te beheren.
7. Uitbreiding voor meerdere UI-toestanden
Omdat je meerdere UI-toestanden hebt, kun je submachines of parallelle staten gebruiken (bijvoorbeeld via `type: 'parallel'`). Dit maakt je toestandsmachine schaalbaar en overzichtelijk.
**Kort samengevat:**
- Kies een bibliotheek als XState voor declaratieve definities.
- Maak je toestanden, gebeurtenissen, en overgangen expliciet.
- Implementeer in Svelte met `useMachine`.
- Houd je machine gescheiden van UI voor betere onderhoudbaarheid.
- Gebruik parallelle of geneste toestanden voor complexe UI-gedrag.
Mocht je een voorbeeld willen van de volledige toestandsmachine voor jouw specifieke registratieproces, laat het gerust weten!
provider: deepseek
model: deepseek-chat
Hier is een gestructureerde aanpak voor het opzetten van een toestandsmachine in je Svelte-applicatie voor gebruikersregistratie met e-mailverificatie:
## 1. Definieer de Toestandsmachine
```javascript
// stores/registrationMachine.js
import { writable } from 'svelte/store';
// Definieer alle mogelijke toestanden
export const STATES = {
INITIAL: 'initial',
FORM_VALIDATION: 'form_validation',
SUBMITTING: 'submitting',
SUCCESS: 'success',
EMAIL_SENT: 'email_sent',
VERIFYING: 'verifying',
VERIFIED: 'verified',
ERROR: 'error'
};
// Definieer gebeurtenissen (events)
export const EVENTS = {
START_REGISTRATION: 'START_REGISTRATION',
FORM_VALID: 'FORM_VALID',
FORM_INVALID: 'FORM_INVALID',
SUBMIT: 'SUBMIT',
SUBMIT_SUCCESS: 'SUBMIT_SUCCESS',
SUBMIT_ERROR: 'SUBMIT_ERROR',
EMAIL_SENT: 'EMAIL_SENT',
START_VERIFICATION: 'START_VERIFICATION',
VERIFICATION_SUCCESS: 'VERIFICATION_SUCCESS',
VERIFICATION_ERROR: 'VERIFICATION_ERROR',
RESET: 'RESET'
};
// Toestandsovergangen definiëren
const transitions = {
[STATES.INITIAL]: {
[EVENTS.START_REGISTRATION]: STATES.FORM_VALIDATION
},
[STATES.FORM_VALIDATION]: {
[EVENTS.FORM_VALID]: STATES.SUBMITTING,
[EVENTS.FORM_INVALID]: STATES.FORM_VALIDATION
},
[STATES.SUBMITTING]: {
[EVENTS.SUBMIT_SUCCESS]: STATES.EMAIL_SENT,
[EVENTS.SUBMIT_ERROR]: STATES.ERROR
},
[STATES.EMAIL_SENT]: {
[EVENTS.START_VERIFICATION]: STATES.VERIFYING
},
[STATES.VERIFYING]: {
[EVENTS.VERIFICATION_SUCCESS]: STATES.VERIFIED,
[EVENTS.VERIFICATION_ERROR]: STATES.ERROR
},
[STATES.ERROR]: {
[EVENTS.RESET]: STATES.INITIAL
},
[STATES.VERIFIED]: {
[EVENTS.RESET]: STATES.INITIAL
}
};
```
## 2. Maak de Toestandsmachine Store
```javascript
// stores/registrationMachine.js (vervolg)
function createRegistrationMachine() {
const { subscribe, set, update } = writable({
currentState: STATES.INITIAL,
context: {
userData: null,
error: null,
verificationToken: null
}
});
return {
subscribe,
send: (event, data = {}) => {
update(state => {
const nextState = transitions[state.currentState]?.[event];
if (!nextState) {
console.warn(`Ongeldige overgang: ${state.currentState} -> ${event}`);
return state;
}
// Update context gebaseerd op event en data
const newContext = { ...state.context, ...data };
// Specifieke context updates per event
if (event === EVENTS.SUBMIT_ERROR || event === EVENTS.VERIFICATION_ERROR) {
newContext.error = data.error;
}
if (event === EVENTS.SUBMIT_SUCCESS) {
newContext.verificationToken = data.verificationToken;
}
return {
currentState: nextState,
context: newContext
};
});
},
reset: () => set({
currentState: STATES.INITIAL,
context: {
userData: null,
error: null,
verificationToken: null
}
})
};
}
export const registrationMachine = createRegistrationMachine();
```
## 3. Hoofd Registratie Component
```svelte
<!-- Registration.svelte -->
<script>
import { registrationMachine, STATES, EVENTS } from './stores/registrationMachine';
import RegistrationForm from './RegistrationForm.svelte';
import Verification from './Verification.svelte';
import Success from './Success.svelte';
import Error from './Error.svelte';
import Loading from './Loading.svelte';
let currentState;
let context;
$: {
currentState = $registrationMachine.currentState;
context = $registrationMachine.context;
}
</script>
<div class="registration-flow">
{#if currentState === STATES.INITIAL || currentState === STATES.FORM_VALIDATION}
<RegistrationForm bind:currentState bind:context />
{:else if currentState === STATES.SUBMITTING}
<Loading message="Account aanmaken..." />
{:else if currentState === STATES.EMAIL_SENT}
<Verification bind:currentState bind:context />
{:else if currentState === STATES.VERIFYING}
<Loading message="E-mail verifiëren..." />
{:else if currentState === STATES.VERIFIED}
<Success bind:context />
{:else if currentState === STATES.ERROR}
<Error bind:context on:reset={() => registrationMachine.send(EVENTS.RESET)} />
{/if}
</div>
```
## 4. Formulier Component
```svelte
<!-- RegistrationForm.svelte -->
<script>
import { STATES, EVENTS } from './stores/registrationMachine';
import { registrationMachine } from './stores/registrationMachine';
export let currentState;
export let context;
let email = '';
let password = '';
let formErrors = {};
function validateForm() {
formErrors = {};
if (!email.includes('@')) {
formErrors.email = 'Ongeldig e-mailadres';
}
if (password.length < 8) {
formErrors.password = 'Wachtwoord moet minimaal 8 karakters zijn';
}
return Object.keys(formErrors).length === 0;
}
async function handleSubmit() {
if (!validateForm()) {
return;
}
registrationMachine.send(EVENTS.FORM_VALID);
registrationMachine.send(EVENTS.SUBMIT, { userData: { email, password } });
try {
// API call voor registratie
const result = await registerUser({ email, password });
registrationMachine.send(EVENTS.SUBMIT_SUCCESS, {
verificationToken: result.verificationToken
});
} catch (error) {
registrationMachine.send(EVENTS.SUBMIT_ERROR, { error: error.message });
}
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<div class="form-group">
<label for="email">E-mail:</label>
<input
type="email"
id="email"
bind:value={email}
disabled={currentState === STATES.SUBMITTING}
/>
{#if formErrors.email}
<span class="error">{formErrors.email}</span>
{/if}
</div>
<div class="form-group">
<label for="password">Wachtwoord:</label>
<input
type="password"
id="password"
bind:value={password}
disabled={currentState === STATES.SUBMITTING}
/>
{#if formErrors.password}
<span class="error">{formErrors.password}</span>
{/if}
</div>
<button
type="submit"
disabled={currentState === STATES.SUBMITTING}
>
{currentState === STATES.SUBMITTING ? 'Bezig...' : 'Registreren'}
</button>
</form>
```
## 5. Type Safety en Onderhoudbaarheid
```javascript
// types/registration.js
/**
* @typedef {Object} RegistrationContext
* @property {Object|null} userData
* @property {string|null} error
* @property {string|null} verificationToken
*/
/**
* @typedef {Object} RegistrationState
* @property {string} currentState
* @property {RegistrationContext} context
*/
// Validatie functie
export function isValidTransition(currentState, event) {
return Boolean(transitions[currentState]?.[event]);
}
```
## 6. Teststrategie
```javascript
// tests/registrationMachine.test.js
import { registrationMachine, STATES, EVENTS } from '../stores/registrationMachine';
describe('Registration Machine', () => {
it('should transition from INITIAL to FORM_VALIDATION on START_REGISTRATION', () => {
registrationMachine.send(EVENTS.START_REGISTRATION);
// Assert state change
// Test context updates
});
});
```
## Belangrijke Voordelen:
1. **Duidelijke Toestandsovergangen**: Elke mogelijke staat is expliciet gedefinieerd
2. **Onderhoudbaarheid**: Wijzigingen in de flow vereisen alleen aanpassingen in de transitions
3. **Foutafhandeling**: Geïsoleerde error states voorkomen onverwachte UI-gedrag
4. **Testbaarheid**: Elke overgang kan afzonderlijk worden getest
5. **Schaalbaarheid**: Nieuwe toestanden en events zijn eenvoudig toe te voegen
Deze aanpak zorgt voor een robuuste en onderhoudbare registratieflow die eenvoudig aan te passen is aan toekomstige vereisten.

