On this pageOverview
Model as Union
When your app has mutually exclusive states—like logged in vs logged out, wizard steps, or game phases—you can model your root state as a union of variants rather than embedding submodels in a struct.
Define each variant as a tagged struct, then combine them with S.Union:
import { Schema as S } from 'effect'
import { ts } from 'foldkit/schema'
const LoggedOut = ts('LoggedOut', {
email: S.String,
password: S.String,
})
const LoggedIn = ts('LoggedIn', {
userId: S.String,
username: S.String,
})
export const Model = S.Union(LoggedOut, LoggedIn)
export type Model = typeof Model.TypeIn the view, use Match.tagsExhaustive to handle each variant:
import { Match as M } from 'effect'
export const view = (model: Model) =>
M.value(model).pipe(
M.tagsExhaustive({
LoggedOut: renderLoginForm,
LoggedIn: renderDashboard,
}),
)To transition between states, return a different variant from update:
import { Match as M } from 'effect'
import { Command } from 'foldkit/command'
export const update = (
model: Model,
message: Message,
): [Model, ReadonlyArray<Command<Message>>] =>
M.value(message).pipe(
M.tagsExhaustive({
ClickedLogin: () => [
LoggedIn({ userId: '123', username: 'alice' }),
[],
],
ClickedLogout: () => [
LoggedOut({ email: '', password: '' }),
[],
],
}),
)See the Auth example for a complete implementation.
If you need shared state across union variants, wrap the union in a struct:
import { Schema as S } from 'effect'
export const Model = S.Struct({
theme: S.String,
authState: S.Union(LoggedOut, LoggedIn),
})
export type Model = typeof Model.Type