React / foldkit-react-codegen

Sixty seconds

Start with two files so the generated view's relative import resolves:

ts
// src/message.ts
import { defineMessageUnion } from 'foldkit/message'

export type Model = { readonly saving: boolean }
export const Message = defineMessageUnion({ Saved: {} })
export type Message = typeof Message.Type
ts
// src/SaveButton.ts
import type { Html, HtmlBuilder } from 'foldkit/html'
import { Message, type Model } from './message'

export const view = (model: Model, h: HtmlBuilder<Message>): Html =>
  h.button([h.Class('save'), h.Disabled(model.saving), h.OnClick(Message.Saved())], ['Save'])
sh
pnpm foldkit-react-codegen src --out-dir generated
tsx
// generated/src/SaveButton.tsx
// @generated by foldkit-react-codegen from src/SaveButton.ts. Do not edit.
import type { ReactNode } from "react";
import { Message, type Model } from './message';
export const view = (model: Model, dispatch: (message: Message) => void): ReactNode => (<button className={'save'} disabled={model.saving} onClick={() => dispatch(Message.Saved())}>Save</button>);

The compiler also copies src/message.ts under generated/src, preserving the relative import. The output needs React and its type declarations in the consuming project, and a TypeScript configuration with JSX enabled.

Use it inside a React component (integration fragment): import the generated view and React's useState, and supply your initialModel and transition:

tsx
const [model, setModel] = useState(initialModel)
// `next` is your transition: (model, message) => model
return view(model, message => setModel(current => next(current, message)))

What the compiler did:

  • Every function with an HtmlBuilder parameter is a view. Its builder parameter became dispatch, and its Html types became ReactNode. A helper view called as row(item, h) becomes row(item, dispatch), so views still compose.
  • h.<tag>(attributes, children) became a JSX element, and each attribute became a prop using the table below.
  • Everything else, such as the ternaries, map calls, local helpers, and imports, is copied through unchanged.

The output is not formatted. Run your formatter over the out dir if you commit it.

Check the handoff

After generation, run the consuming React project's typecheck and render the button there. With saving: true it should be disabled; otherwise a click should call dispatch with Saved. Code generation does not implement the save or execute Foldkit Commands returned by an application update.

Keep generated files separate from their source. A diagnostic stops the current write, and watch mode retains the last good output. Check diagnostics before testing output that may belong to an earlier successful build.