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:
| React | miso | The difference |
|---|---|---|
useState | the model + Miso.Lens | one model per component; lenses are the getters / setters; updates are pure |
useEffect | Effect | IO is scheduled from update, results return as actions; no dependency arrays |
useReducer | update | not opt-in: every component is a reducer |
useRef | onCreated / onCreatedWith | lifecycle 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 .= 0Because 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 .= uNo 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 theuseContexthook 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 themountUseContextshorthand. See Context. - Props — read-only data a parent passes to a child (
mountWithProps_); the child re-renders when they change and can react viaonPropsChanged. See Props. - Fragment —
vfragis<></>: 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 ]