|
256 | 256 | -- |
257 | 257 | -- 'startApp' and 'miso' will always infer @parent@ as 'ROOT'. |
258 | 258 | -- |
259 | | --- = Props |
260 | | --- |
261 | | --- Inspired by [React props](https://react.dev/learn/passing-props-to-a-component), |
262 | | --- @miso@ allows a parent 'Component' to pass read-only data down to a child 'Component' |
263 | | --- via a mechanism called /props/ (short for /properties/). |
264 | | --- |
265 | | --- == Props vs. Component-local state |
266 | | --- |
267 | | --- * __model__: Component-local state. It is owned and mutated exclusively by the 'Component' |
268 | | --- itself through its 'Miso.Types.update' function. No other 'Component' can write to it directly. |
269 | | --- |
270 | | --- * __props__: Data /inherited/ from the @parent@ 'Component'. Props flow downward through |
271 | | --- the component hierarchy and are read-only from the child's perspective. The parent decides |
272 | | --- what props to pass at mount time; the child cannot mutate them. Props that change in a |
273 | | --- the parent cause the child to re-render. |
274 | | --- |
275 | | --- This mirrors the distinction in React between component state (@useState@) and props |
276 | | --- received from above (@function MyComponent({ name }) { ... }@). |
277 | | --- |
278 | | --- === When to use props |
279 | | --- |
280 | | --- Props are best suited for /metadata/ — contextual or configuration data that the child needs |
281 | | --- to know about but should not own. Good examples: a user's display name, a theme token, a |
282 | | --- locale string, or a read-only identifier used to customise rendering. |
283 | | --- |
284 | | --- If the data drives the child's own business logic — counters it increments, form fields it |
285 | | --- edits, async state it manages — that data belongs in the child's @model@ instead. Putting |
286 | | --- mutable business-logic state in @props@ would require the parent to own and thread through |
287 | | --- every change, creating unnecessary coupling. Prefer @props@ for \"what the child should |
288 | | --- know\" and @model@ for \"what the child should do\". |
289 | | --- |
290 | | --- == Props in 'Miso.Types.view' |
291 | | --- |
292 | | --- The 'Miso.Types.view' field of a 'Component' always takes @props@ as its first argument: |
293 | | --- |
294 | | --- @ |
295 | | --- view :: props -> model -> 'View' model action |
296 | | --- @ |
297 | | --- |
298 | | --- Top-level applications have no parent, so @props@ is always @()@: |
299 | | --- |
300 | | --- @ |
301 | | --- view :: () -> model -> 'View' model action |
302 | | --- view _props model = … |
303 | | --- @ |
304 | | --- |
305 | | --- == Props in 'Effect' \/ 'Miso.Types.update' |
306 | | --- |
307 | | --- Use 'getProps' inside the 'Effect' monad to read the current value of @props@: |
308 | | --- |
309 | | --- @ |
310 | | --- update :: Action -> 'Effect' parent props Model Action |
311 | | --- update = \\case |
312 | | --- SomeAction -> do |
313 | | --- p <- 'getProps' |
314 | | --- 'io_' ('consoleLog' (ms (show p))) |
315 | | --- @ |
316 | | --- |
317 | | --- Alternatively, use the 'Miso.Lens.view' combinator with the 'props' lens: |
318 | | --- |
319 | | --- @ |
320 | | --- update = \\case |
321 | | --- SomeAction -> do |
322 | | --- p <- 'Miso.Lens.view' 'props' |
323 | | --- … |
324 | | --- @ |
325 | | --- |
326 | | --- == 'ROOT' — the top-level 'Component' |
327 | | --- |
328 | | --- When a 'Component' is passed to 'startApp' (or 'miso') it has no parent. |
329 | | --- The @parent@ type is specialized to 'ROOT' and @props@ is fixed to @()@: |
330 | | --- |
331 | | --- @ |
332 | | --- type 'App' model action = 'Component' 'ROOT' () model action |
333 | | --- @ |
334 | | --- |
335 | | --- Because there is no parent to inherit from, @props@ will always be @()@ for a |
336 | | --- root-level 'Component'. You can simply ignore the first argument in 'view' and |
337 | | --- skip 'getProps' in 'Miso.Types.update'. |
338 | | --- |
339 | | --- == Passing props to a child 'Component' |
340 | | --- |
341 | | --- Use 'mountWithProps_' (keyed) or 'mountWithProps' (unkeyed) in the parent's 'view' to |
342 | | --- mount a child and supply its props: |
343 | | --- |
344 | | --- @ |
345 | | --- 'mountWithProps_' |
346 | | --- :: ('Eq' child, 'Eq' props) |
347 | | --- => 'MisoString' |
348 | | --- -> props |
349 | | --- -> 'Component' parent props child action |
350 | | --- -> 'View' parent a |
351 | | --- @ |
352 | | --- |
353 | | --- == Example: child reading parent-supplied props |
354 | | --- |
355 | | --- The following shows a parent 'Component' that maintains a greeting string in its |
356 | | --- @model@ and passes it as @props@ to a child 'Component'. The child renders the |
357 | | --- greeting and can also read it from within its 'Miso.Types.update' function. |
358 | | --- |
359 | | --- @ |
360 | | --- ----------------------------------------------------------------------------- |
361 | | --- -- The props type: what the parent shares with the child |
362 | | --- newtype Greeting = Greeting 'MisoString' deriving ('Eq') |
363 | | --- ----------------------------------------------------------------------------- |
364 | | --- -- Child component |
365 | | --- -- |
366 | | --- -- parent props model action |
367 | | --- -- | | | | |
368 | | --- child :: 'Component' ParentModel Greeting () ChildAction |
369 | | --- child = 'vcomp' () updateChild viewChild |
370 | | --- where |
371 | | --- viewChild :: Greeting -> () -> 'View' () ChildAction |
372 | | --- viewChild (Greeting g) _ = |
373 | | --- 'div_' [] [ 'text' ("Hello, " <> g <> "!") ] |
374 | | --- |
375 | | --- updateChild :: ChildAction -> 'Effect' ParentModel Greeting () ChildAction |
376 | | --- updateChild = \\case |
377 | | --- ReadGreeting -> do |
378 | | --- Greeting g <- 'getProps' |
379 | | --- 'io_' ('consoleLog' g) |
380 | | --- ----------------------------------------------------------------------------- |
381 | | --- -- Parent component: owns the greeting, passes it to the child as props |
382 | | --- parent :: 'App' ParentModel ParentAction |
383 | | --- parent = 'vcomp' (ParentModel "World") 'noop' viewParent |
384 | | --- where |
385 | | --- viewParent :: () -> ParentModel -> 'View' ParentModel ParentAction |
386 | | --- viewParent _ (ParentModel g) = |
387 | | --- 'mountWithProps_' "child" (Greeting g) child |
388 | | --- ----------------------------------------------------------------------------- |
389 | | --- newtype ParentModel = ParentModel 'MisoString' deriving ('Eq') |
390 | | --- data ChildAction = ReadGreeting |
391 | | --- data ParentAction |
392 | | --- @ |
393 | | --- |
394 | | --- A few things to notice: |
395 | | --- |
396 | | --- * The child's @parent@ type parameter is @ParentModel@. This must match the |
397 | | --- parent 'Component' @model@ type — 'mountWithProps_' enforces this at compile time. |
398 | | --- * 'getProps' inside the child's 'Miso.Types.update' yields a @Greeting@, not |
399 | | --- the full @ParentModel@. The child only sees what the parent explicitly chose to share. |
400 | | --- * The root 'App' always has @props ~ ()@; no extra plumbing is needed when calling 'startApp'. |
401 | | --- |
402 | 259 | -- = 'VComp' lifecycle hooks |
403 | 260 | -- |
404 | 261 | -- 'Component' are mounted on the fly during diffing. All t'Component` are equipped with `mount` and `unmount` hooks. This allows the defining of custom actions that will be processed in response to lifecycle events. |
|
459 | 316 | -- |
460 | 317 | -- @ |
461 | 318 | -- -- Keyed fragment — survives reordering without full teardown\/remount |
462 | | --- 'vfrag_' "my-key" [ 'li_' [] [ 'text' "A" ], 'li_' [] [ 'text' "B" ] ] |
| 319 | +-- 'vfrag_' "my-key" [ 'li_' [] [ 'text' "Item A" ], 'li_' [] [ 'text' "Item B" ] ] |
463 | 320 | -- @ |
464 | 321 | -- |
465 | 322 | -- Fragments may be nested — a 'VFrag' child may itself be a 'VFrag'. The diff function |
|
617 | 474 | -- |
618 | 475 | -- = 'Component' communication |
619 | 476 | -- |
| 477 | +-- = Props |
| 478 | +-- |
| 479 | +-- Inspired by [React props](https://react.dev/learn/passing-props-to-a-component), |
| 480 | +-- @miso@ allows a parent 'Component' to pass read-only data down to a child 'Component' |
| 481 | +-- via a mechanism called /props/ (short for /properties/). |
| 482 | +-- |
| 483 | +-- == Props vs. Component-local state |
| 484 | +-- |
| 485 | +-- * __model__: Component-local state. It is owned and mutated exclusively by the 'Component' |
| 486 | +-- itself through its 'Miso.Types.update' function. No other 'Component' can write to it directly. |
| 487 | +-- |
| 488 | +-- * __props__: Data /inherited/ from the @parent@ 'Component'. Props flow downward through |
| 489 | +-- the component hierarchy and are read-only from the child's perspective. The parent decides |
| 490 | +-- what props to pass at mount time; the child cannot mutate them. Props that change in a |
| 491 | +-- the parent cause the child to re-render. |
| 492 | +-- |
| 493 | +-- This mirrors the distinction in React between component state (@useState@) and props |
| 494 | +-- received from above (@function MyComponent({ name }) { ... }@). |
| 495 | +-- |
| 496 | +-- === When to use props |
| 497 | +-- |
| 498 | +-- Props are best suited for /metadata/ — contextual or configuration data that the child needs |
| 499 | +-- to know about but should not own. Good examples: a user's display name, a theme token, a |
| 500 | +-- locale string, or a read-only identifier used to customise rendering. |
| 501 | +-- |
| 502 | +-- If the data drives the child's own business logic — counters it increments, form fields it |
| 503 | +-- edits, async state it manages — that data belongs in the child's @model@ instead. Putting |
| 504 | +-- mutable business-logic state in @props@ would require the parent to own and thread through |
| 505 | +-- every change, creating unnecessary coupling. Prefer @props@ for \"what the child should |
| 506 | +-- know\" and @model@ for \"what the child should do\". |
| 507 | +-- |
| 508 | +-- == Props in 'Miso.Types.view' |
| 509 | +-- |
| 510 | +-- The 'Miso.Types.view' field of a 'Component' always takes @props@ as its first argument: |
| 511 | +-- |
| 512 | +-- @ |
| 513 | +-- view :: props -> model -> 'View' model action |
| 514 | +-- @ |
| 515 | +-- |
| 516 | +-- Top-level applications have no parent, so @props@ is always @()@: |
| 517 | +-- |
| 518 | +-- @ |
| 519 | +-- view :: () -> model -> 'View' model action |
| 520 | +-- view _props model = … |
| 521 | +-- @ |
| 522 | +-- |
| 523 | +-- == Props in 'Effect' \/ 'Miso.Types.update' |
| 524 | +-- |
| 525 | +-- Use 'getProps' inside the 'Effect' monad to read the current value of @props@: |
| 526 | +-- |
| 527 | +-- @ |
| 528 | +-- update :: Action -> 'Effect' parent props Model Action |
| 529 | +-- update = \\case |
| 530 | +-- SomeAction -> do |
| 531 | +-- p <- 'getProps' |
| 532 | +-- 'io_' ('consoleLog' (ms (show p))) |
| 533 | +-- @ |
| 534 | +-- |
| 535 | +-- Alternatively, use the 'Miso.Lens.view' combinator with the 'props' lens: |
| 536 | +-- |
| 537 | +-- @ |
| 538 | +-- update = \\case |
| 539 | +-- SomeAction -> do |
| 540 | +-- p <- 'Miso.Lens.view' 'props' |
| 541 | +-- … |
| 542 | +-- @ |
| 543 | +-- |
| 544 | +-- == 'ROOT' — the top-level 'Component' |
| 545 | +-- |
| 546 | +-- When a 'Component' is passed to 'startApp' (or 'miso') it has no parent. |
| 547 | +-- The @parent@ type is specialized to 'ROOT' and @props@ is fixed to @()@: |
| 548 | +-- |
| 549 | +-- @ |
| 550 | +-- type 'App' model action = 'Component' 'ROOT' () model action |
| 551 | +-- @ |
| 552 | +-- |
| 553 | +-- Because there is no parent to inherit from, @props@ will always be @()@ for a |
| 554 | +-- root-level 'Component'. You can simply ignore the first argument in 'view' and |
| 555 | +-- skip 'getProps' in 'Miso.Types.update'. |
| 556 | +-- |
| 557 | +-- == Passing props to a child 'Component' |
| 558 | +-- |
| 559 | +-- Use 'mountWithProps_' (keyed) or 'mountWithProps' (unkeyed) in the parent's 'view' to |
| 560 | +-- mount a child and supply its props: |
| 561 | +-- |
| 562 | +-- @ |
| 563 | +-- 'mountWithProps_' |
| 564 | +-- :: ('Eq' child, 'Eq' props) |
| 565 | +-- => 'MisoString' |
| 566 | +-- -> props |
| 567 | +-- -> 'Component' parent props child action |
| 568 | +-- -> 'View' parent a |
| 569 | +-- @ |
| 570 | +-- |
| 571 | +-- == Example: child reading parent-supplied props |
| 572 | +-- |
| 573 | +-- The following shows a parent 'Component' that maintains a greeting string in its |
| 574 | +-- @model@ and passes it as @props@ to a child 'Component'. The child renders the |
| 575 | +-- greeting and can also read it from within its 'Miso.Types.update' function. |
| 576 | +-- |
| 577 | +-- @ |
| 578 | +-- ----------------------------------------------------------------------------- |
| 579 | +-- -- The props type: what the parent shares with the child |
| 580 | +-- newtype Greeting = Greeting 'MisoString' deriving ('Eq') |
| 581 | +-- ----------------------------------------------------------------------------- |
| 582 | +-- -- Child component |
| 583 | +-- -- |
| 584 | +-- -- parent props model action |
| 585 | +-- -- | | | | |
| 586 | +-- child :: 'Component' ParentModel Greeting () ChildAction |
| 587 | +-- child = 'vcomp' () updateChild viewChild |
| 588 | +-- where |
| 589 | +-- viewChild :: Greeting -> () -> 'View' () ChildAction |
| 590 | +-- viewChild (Greeting g) _ = |
| 591 | +-- 'div_' [] [ 'text' ("Hello, " <> g <> "!") ] |
| 592 | +-- |
| 593 | +-- updateChild :: ChildAction -> 'Effect' ParentModel Greeting () ChildAction |
| 594 | +-- updateChild = \\case |
| 595 | +-- ReadGreeting -> do |
| 596 | +-- Greeting g <- 'getProps' |
| 597 | +-- 'io_' ('consoleLog' g) |
| 598 | +-- ----------------------------------------------------------------------------- |
| 599 | +-- -- Parent component: owns the greeting, passes it to the child as props |
| 600 | +-- parent :: 'App' ParentModel ParentAction |
| 601 | +-- parent = 'vcomp' (ParentModel "World") 'noop' viewParent |
| 602 | +-- where |
| 603 | +-- viewParent :: () -> ParentModel -> 'View' ParentModel ParentAction |
| 604 | +-- viewParent _ (ParentModel g) = |
| 605 | +-- 'mountWithProps_' "child" (Greeting g) child |
| 606 | +-- ----------------------------------------------------------------------------- |
| 607 | +-- newtype ParentModel = ParentModel 'MisoString' deriving ('Eq') |
| 608 | +-- data ChildAction = ReadGreeting |
| 609 | +-- data ParentAction |
| 610 | +-- @ |
| 611 | +-- |
| 612 | +-- A few things to notice: |
| 613 | +-- |
| 614 | +-- * The child's @parent@ type parameter is @ParentModel@. This must match the |
| 615 | +-- parent 'Component' @model@ type — 'mountWithProps_' enforces this at compile time. |
| 616 | +-- * 'getProps' inside the child's 'Miso.Types.update' yields a @Greeting@, not |
| 617 | +-- the full @ParentModel@. The child only sees what the parent explicitly chose to share. |
| 618 | +-- * The root 'App' always has @props ~ ()@; no extra plumbing is needed when calling 'startApp'. |
| 619 | +-- |
| 620 | +-- |
620 | 621 | -- == Asynchronous communication |
621 | 622 | -- |
622 | 623 | -- 'Component' are able to communicate asynchronously via a message-passing system. |
|
0 commit comments