Skip to main content
A Component is the fundamental building block of a miso application. It encapsulates state, update logic, and view rendering, and can be nested inside other components to form a typed tree.

The Component type

data Component parent model action
  = Component
  { model            :: model
  -- ^ Initial model
  , hydrateModel     :: Maybe (IO model)
  -- ^ Loads model state for hydration only
  , update           :: action -> Effect parent model action
  -- ^ Updates model and schedules effects
  , view             :: model -> View model action
  -- ^ Renders the virtual DOM
  , subs             :: [Sub action]
  -- ^ Long-running subscriptions (stopped on unmount)
  , styles           :: [CSS]
  -- ^ CSS injected into <head> before first draw
  , scripts          :: [JS]
  -- ^ JS injected into <head> before first draw
  , mountPoint       :: Maybe MountPoint
  -- ^ Root element ID for DOM diffing (Nothing = <body>)
  , logLevel         :: LogLevel
  -- ^ Debugging: Off | DebugHydrate | DebugEvents | DebugAll
  , mailbox          :: Value -> Maybe action
  -- ^ Receives JSON messages from other components
  , bindings         :: [Binding parent model]
  -- ^ Synchronous data bindings with parent model
  , eventPropagation :: Bool
  -- ^ Whether events bubble past the component boundary (default: False)
  , mount            :: Maybe action
  -- ^ Action dispatched when component mounts
  , unmount          :: Maybe action
  -- ^ Action dispatched when component unmounts
  }

Smart constructors

Use component (or its synonym vcomp) to create a component with all optional fields set to safe defaults:
component
  :: model
  -> (action -> Effect parent model action)
  -> (model -> View model action)
  -> Component parent model action
Defaults applied by component:
  • subs = [], styles = [], scripts = []
  • mountPoint = Nothing (uses <body>)
  • logLevel = Off
  • mailbox = const Nothing
  • bindings = []
  • eventPropagation = False
  • mount = Nothing, unmount = Nothing
Record update syntax lets you override any field:
myApp :: App Int Action
myApp = component 0 updateModel viewModel
  { logLevel = DebugAll
  , mount    = Just Init
  }

App vs Component

App is a type alias for a top-level Component with no parent:
type App model action = Component ROOT model action

-- ROOT is an uninhabited phantom type
data ROOT
startApp and miso always infer parent ~ ROOT. Child components use the parent’s model type as their parent type parameter, giving them type-safe read-only access to the parent state.

Component composition

Components are embedded in the View tree using the (+>) combinator or mount_.

(+>) — keyed mounting

(+>)
  :: forall child model action a . Eq child
  => MisoString          -- ^ Unique key for diffing
  -> Component model child action
  -> View model a
The key string uniquely identifies the component at runtime. When two components are diffed and their keys differ, the old component is unmounted and a new one is mounted:
view :: Int -> View Int action
view x = div_ [ id_ "container" ] [ "counter" +> counter ]

mount_ — keyless mounting

mount_ :: Eq child => Component model child a -> View model action
Only use mount_ when you are certain a single instance of this component will always occupy the same position in the view. When diffing could encounter multiple components of the same type, always use (+>) with a unique key.

Multiple components in a list

ul_
  []
  [ "key-1" +> counter
  , "key-2" +> timer
  , "key-3" +> logger
  ]

Lifecycle hooks

Component-level hooks

Set mount and unmount to dispatch an action when the component is inserted into or removed from the DOM:
data Action = Init | Cleanup

main :: IO ()
main = startApp defaultEvents counter { mount = Just Init }

update :: Action -> Effect parent model Action
update = \case
  Init    -> io_ (consoleLog "hello world!")
  Cleanup -> io_ (consoleLog "goodbye!")

VNode-level hooks

Individual DOM nodes expose four lifecycle events:
HookTrigger
onBeforeCreatedBefore the DOM node is created
onCreated / onCreatedWithAfter the DOM node is inserted
onBeforeDestroyed / onBeforeDestroyedWithBefore the DOM node is removed
onDestroyedAfter the DOM node is removed
The *With variants provide the DOMRef of the target node as a callback argument. This is useful for integrating third-party JavaScript libraries:
{-# LANGUAGE QuasiQuotes     #-}
{-# LANGUAGE MultilineStrings #-}

import Miso
import Miso.FFI.QQ (js)

data Action = Highlight DOMRef

update :: Action -> Effect parent model Action
update = \case
  Highlight domRef -> io_ $ do
    [js| hljs.highlight(${domRef}) |]

view :: model -> View model Action
view x =
  code_
  [ onCreatedWith Highlight
  ]
  [ """
    function addOne (x) { return x + 1; }
    """
  ]

Keys

A Key is a unique identifier used to optimize diffing. Providing keys serves two purposes:
  1. List diffing optimization — when all children in a list have unique keys, miso can reorder nodes instead of destroying and recreating them.
  2. Component identity — when two VComp nodes are compared and their keys differ, the old component is unmounted and the new one is freshly mounted.
ul_
  []
  [ li_      [ key_ "key-1" ] [ "a" ]
  , li_      [ key_ "key-2" ] [ "b" ]
  , "key-3" +> counter
  , textKey  "key-4" "text here"
  ]

Component mailbox

Every component has a mailbox field that receives JSON messages from other components. Pair it with checkMail in a subscription, and use mail, mailParent, or broadcast to send messages:
-- Sending a message to a specific component by ID
update = \case
  NotifyChild -> io_ $ do
    compId <- view componentInfoId
    mail compId (toJSON ("hello" :: MisoString))

-- Receiving messages
myComponent = component model updateFn viewFn
  { mailbox = \v -> case fromJSON v of
      Success msg -> Just (Received msg)
      _           -> Nothing
  }

Data bindings

The bindings field enables experimental synchronous data synchronization between a parent and child component model via lenses. See Miso.Binding for the Binding type and combinators (-->, <--, <-->).
Data bindings are experimental as of miso 1.9. The API may change. See the miso-reactive example for usage.

Debugging

Set logLevel to enable runtime warnings:
counter { logLevel = DebugAll }
LevelWhat is logged
OffNothing (default)
DebugHydrateMismatches between real DOM and virtual DOM during hydration
DebugEventsUnroutable events and unregistered event listeners
DebugAllBoth of the above

Build docs developers (and LLMs) love