Core concepts

Props

Inspired by React props, miso allows a parent component to pass read-only data down to a child via props. Props are synchronous: when they change in the parent, the child re-renders.

Props vs component-local state

  • model — component-local state, owned and mutated exclusively by the component through its update. No other component can write to it directly.
  • props — data inherited from the parent. Props flow downward and are read-only from the child's perspective; the parent decides what to pass at mount time.

This mirrors React's distinction between useState and the props a function component receives.

When to use props

Props suit metadata — contextual or configuration data the child needs to know about but should not own: a display name, a theme token, a locale, a read-only identifier. If the data drives the child's own business logic — counters it increments, form fields it edits, async state it manages — it belongs in the child's model. Prefer props for "what the child should know" and the model for "what the child should do".

Props in view and update

As of 1.14.0 view takes only the model; props are read ambiently with vprops (see Ambient accessors). Top-level applications have no parent, so their props are ():

view
  :: model
  -> View context props model action
view _model =
  vprops $ \props -> ...

Use getProps inside Effect (or Miso.Lens.view props) to read the current value:

update = \case
  SomeAction -> do
    p <- getProps
    io_ (consoleLog (ms (show p)))

Passing props to a child

Use mountWithProps_ (keyed) or mountWithProps (unkeyed) in the parent's view:

mountWithProps_
  :: (Eq context, Eq model, Eq props)
  => MisoString
  -> props
  -> Component context props model action
  -> View context props parentModel parentAction

Example: child reading parent-supplied props

-- The props type: what the parent
-- shares with the child
newtype Greeting = Greeting MisoString
  deriving (Eq)

child
  :: Component () Greeting () ChildAction
child = component () updateChild viewChild
  where
    viewChild
      :: ()
      -> View () Greeting () ChildAction
    viewChild _ =
      vprops $ \(Greeting g) ->
        H.div_ []
          [ text ("Hello, " <> g <> "!") ]

    updateChild
      :: ChildAction
      -> Effect () Greeting () ChildAction
    updateChild = \case
      ReadGreeting -> do
        Greeting g <- getProps
        io_ (consoleLog g)

-- Parent component: owns the greeting,
-- passes it to the child as props
parentComp :: App ParentModel ParentAction
parentComp =
  component (ParentModel "World") noop viewParent
  where
    viewParent
      :: ParentModel
      -> View () () ParentModel ParentAction
    viewParent (ParentModel g) =
      mountWithProps_ "child" (Greeting g) child

newtype ParentModel = ParentModel MisoString
  deriving (Eq)

data ChildAction = ReadGreeting
data ParentAction
  • Props flow from parent to child explicitly via mountWithProps_; the child's context is the shared global context.
  • getProps inside the child's update yields a Greeting. The child only sees what the parent chose to share.
  • The root App always has context ~ () and props ~ (); no plumbing is needed for startApp.
  • The onPropsChanged hook dispatches an action with the previous and current props whenever they change.

Try it

Props flowing from a parent to a childlive

Hello, World!

props changed 0 times

-- The props type: what the parent shares with the child.
newtype Greeting = Greeting MisoString
  deriving (Show, Eq)

-- The parent owns the name and passes it down as props.
data NamerAction = NameChanged MisoString

namer
  :: Eq ctx
  => Component ctx () MisoString NamerAction
namer = component "World" update view
  where
    update (NameChanged s) = this .= s

    view name =
      H.div_ []
        [ H.input_
            [ HP.value_ name
            , HE.onInput NameChanged
            , HP.placeholder_ "Your name"
            ]
        , mountWithProps_ "greeter" (Greeting name) greeter
          -- keyed, with props
        ]

-- The child reads its props in view and in update.
data GreeterModel = GreeterModel
  { _changes :: Int
  , _shown   :: MisoString
  } deriving (Show, Eq)

changes :: Lens GreeterModel Int
changes = lens _changes $ \m x -> m { _changes = x }

shown :: Lens GreeterModel MisoString
shown = lens _shown $ \m x -> m { _shown = x }

data GreeterAction
  = ShowProps
  | PropsChanged Greeting Greeting

greeter
  :: Component ctx Greeting GreeterModel GreeterAction
greeter = (component (GreeterModel 0 "") update view)
  { onPropsChanged = Just PropsChanged
    -- react when the parent changes props
  }
  where
    update = \case
      PropsChanged _old _new -> changes += 1
      ShowProps -> do
        Greeting g <- getProps
        -- props are readable in Effect
        shown .= "props are: " <> g

    view m =
      -- props are ambient in the view: read them with 'vprops'
      vprops $ \(Greeting g) ->
        H.div_ []
          [ H.p_ [] [ "Hello, ", H.strong_ [] [ text g ], "!" ]
          , H.p_ [ HP.class_ "muted" ]
              [ "props changed ", text (ms (m ^. changes)), " times" ]
          , H.button_ [ HE.onClick ShowProps ]
              [ "show props" ]
          , H.p_ [] [ text (m ^. shown) ]
          ]