miso

Thinking in miso

miso vs. React

If you come from React, most of your instincts carry over: components, props, context, fragments, keys and a virtual DOM all work the way you expect. The hooks do not — their jobs exist in miso, but they are done by different constructs. Below, the false friends (same job, different shape) and the friends (same name, same idea).

False friends

Each of these hooks solves a problem miso solves elsewhere:

ReactmisoThe difference
useStatethe model + Miso.Lensone model per component; lenses are the getters / setters; updates are pure
useEffectEffectIO is scheduled from update, results return as actions; no dependency arrays
useReducerupdatenot opt-in: every component is a reducer
useRefonCreated / onCreatedWithlifecycle hooks hand you the DOMRef directly

useState → the model and Miso.Lens

There is no per-hook state cell. A component's state is its model — one plain Haskell value — and Miso.Lens generates the getter / setter pairs. Writes happen in exactly one place (update), purely, with the lens operators:

data Model = Model { _count :: Int }
  deriving (Show, Eq)

count :: Lens Model Int
count = lens _count $ \m x -> m { _count = x }

update = \case
  Increment -> count += 1
  Reset     -> count .= 0

Because the setter is not a function you thread through your render, there are no stale closures and no batching surprises — view always sees the current model.

useEffect → Effect

update runs in the Effect monad. IO is never performed inline; it is scheduled with io / io_ and its result comes back as another action, folded in like any other:

update = \case
  FetchUser uid ->
    io (GotUser <$> lookupUser uid)
  GotUser u ->
    user .= u

No dependency arrays, no effect re-run rules, no cleanup functions to remember: long-running concerns are subscriptions, which stop automatically when the component unmounts. See Effects.

useReducer → update

The closest cousin — except it is not an opt-in pattern. Every miso component is a reducer: the action type is your action union, update is the reducer and the model is the state. Step 3 of this guide is exactly the useReducer mindset, applied everywhere.

useRef → lifecycle hooks and DOMRef

Where React reaches for useRef to hold a DOM node, miso's element lifecycle hooks hand the node to you: onCreatedWith dispatches an action carrying the DOMRef when the element is created (and onDestroyed when it goes away):

view _ _ _ =
  H.canvas_ [ onCreatedWith SetupChart ] []

update = \case
  SetupChart ref ->
    io_ (initChart ref)

The ref is an ordinary action payload — store it in the model if you need it later. There is no .current escape hatch to mutate around the render cycle.

Friends

These mean the same thing on both sides of the border:

  • Context — one global value shared by the whole tree, read without prop-drilling; write it with modifyContext. Where React components call the useContext hook to subscribe, in miso any component can subscribe to context changes by enabling the field of the same name — useContext = True — or by mounting with the mountUseContext shorthand. See Context.
  • Props — read-only data a parent passes to a child (mountWithProps_); the child re-renders when they change and can react via onPropsChanged. See Props.
  • Fragmentvfrag is <></>: group siblings without a wrapper element, keyed variants included. See Text & fragments.
  • Keys, components, event delegation, virtual DOM — miso implements the same architecture internals as React, so keys drive reconciliation and events delegate through one root listener, exactly as you are used to.
-- subscribe to context changes, two ways:

child = (component m u v)
  { useContext = True }

-- or at the mount site:
view ctx _ _ =
  H.div_ []
    [ mountUseContext themedBadge ]