08 · Frontend

Does it work with React: components, hooks, and Context?

React is built in. A component compiles to a React function component, JSX lowers to createElement, custom hooks are just functions, and complex state is safer than plain TypeScript.

Why it matters

A lot of the code agents write is React. Glyph treats it as a first-class target, not a bolt-on: component is its own keyword (so grep "^component " audits your whole UI), and hooks work exactly as you'd expect because they're ordinary function calls.

See it

A custom hook is a function that calls hooks. Reducer state gets something React alone can't give you, compile-time exhaustiveness on every action:

counter.glyph: a custom hook + a reducer that can't miss a case
type Action = Inc | Dec | Reset

fn reducer(n: number, a: Action) -> number {
  return match a {
    Inc => n + 1,
    Dec => n - 1,
    Reset => 0,
    // add an Action variant ⇒ this match fails to compile
  }
}

fn use_counter() -> StateHandle<number> {
  return use_reducer(reducer, 0)
}

Add a SetTo(number) action and every reducer that doesn't handle it stops compiling: the bug a plain TypeScript switch would let ship. React Context works today through createContext / useContext, and member-expression element names (<ThemeContext.Provider>) work directly: no workaround.

JSX prop spreading works too: <input {...register("email")} /> lowers to an object spread in the props, so the react-hook-form idiom needs no adapter. Zod's value-derived types are first-class: type FormData = z.infer<typeof schema> works directly, no escape hatch. The one thing that differs: a component takes a single props record, not multiple positional params. See using your libraries and the hardening page for exactly where that boundary is.

Are props checked before tsc?

Yes. Glyph compares each element with its component's props record itself, so a wrong value, a misspelled prop and a missing required one are Glyph errors that name the prop. You see them in the editor and over MCP, not only in a full build. It works the same whether the component was imported by name or through its module:

main.glyph, with Card declared in ui.glyph
import ui

pub component App() {
  return <div>
    <ui.Card title="Q3" />
    <ui.Card title="Q4" cout={2} />
  </div>
}

// E0211 argument type mismatch: expected `CardProps`, found `record`: missing field `count`
// E0210 type `CardProps` has no field `cout`
// E0211 argument type mismatch: expected `CardProps`, found `record`: missing field `count`

--json carries the missing names as a missing_fields list, so an agent can add the props without parsing the message. A component also has to take its props as one record: component Greeting(name: string) is E0229 at the parameter, because React only ever passes the props object and that element could never type-check. If you call it as a function rather than render it, declare it with fn.

Where it stands

Shipping today

Components, JSX, fragments (<>...</>), member-expression element names (<Ctx.Provider>), JSX prop spread ({...register()}), props checked against the component before tsc under every import spelling, built-in and custom hooks, useReducer with exhaustive tagged-union actions, and Context via createContext/useContext.