On this pageOverview
Scaling with Submodels
As your app grows, a single Model/Message/Update becomes unwieldy. The submodel pattern lets you split your app into self-contained modules, each with its own Model, Message, init, update, and view.
Each submodule has its own Model, Message, and update:
import { Match as M, Schema as S } from 'effect'
import { Command } from 'foldkit/command'
import { m } from 'foldkit/message'
import { evo } from 'foldkit/struct'
// MODEL
export const Model = S.Struct({
theme: S.String,
notifications: S.Boolean,
})
export type Model = typeof Model.Type
// MESSAGE
export const ChangedTheme = m('ChangedTheme', { theme: S.String })
export const Message = S.Union(ChangedTheme)
export type Message = typeof Message.Type
// UPDATE
export const update = (
model: Model,
message: Message,
): [Model, ReadonlyArray<Command<Message>>] =>
M.value(message).pipe(
M.tagsExhaustive({
ChangedTheme: ({ theme }) => [
evo(model, { theme: () => theme }),
[],
],
}),
)The parent model embeds the child model as a field:
import { Schema as S } from 'effect'
import * as Settings from './page/settings'
export const Model = S.Struct({
username: S.String,
settings: Settings.Model,
})
export type Model = typeof Model.TypeThe parent has a wrapper message that contains the child message:
import { Schema as S } from 'effect'
import { m } from 'foldkit/message'
import * as Settings from './page/settings'
export const GotSettingsMessage = m('GotSettingsMessage', {
message: Settings.Message,
})
export const Message = S.Union(GotSettingsMessage)
export type Message = typeof Message.TypeIn update, delegate to the child and rewrap returned commands:
import { Array, Effect, Match as M } from 'effect'
import { Command } from 'foldkit/command'
import { evo } from 'foldkit/struct'
export const update = (
model: Model,
message: Message,
): [Model, ReadonlyArray<Command<Message>>] =>
M.value(message).pipe(
M.tagsExhaustive({
GotSettingsMessage: ({ message }) => {
const [nextSettings, commands] = Settings.update(
model.settings,
message,
)
const mappedCommands = Array.map(
commands,
Effect.map(message => GotSettingsMessage({ message })),
)
return [
evo(model, { settings: () => nextSettings }),
mappedCommands,
]
},
}),
)See the Shopping Cart example for a complete implementation of this pattern.