ursi / purescript-elmish

this will have its own name eventually
https://github.com/ursi/purescript-package-set
3 stars 0 forks source link

Incorrect behaviour regarding order- and content-changing updates #5

Open Quelklef opened 3 years ago

Quelklef commented 3 years ago

Bad title because I haven't quite pinned down exactly the core features of this bug. However, I do have a MWE.

Our setup is morally

type Model = Array Box
type Box = { id :: Int, text :: String }
data Msg = Msg Int String

Our initial model is of two boxes with distinct IDs and texts. Our update function accepts a Msg id text and updates the box with the given id to have the given text; additionally, it also swaps the order of the two boxes.

When an update is invoked by a button onClick, handler, the state updates and is re-rendered as expected.

However, when an updated is invoked by textarea onInput handler, though the state updates properly (I have verified this with Debug.log), the re-render is wrong: the content of both boxes is changed, not just one. Additionally, after the re-render, the wrong textarea is focused. See the gif below.

elmish-bug-2

Full MWE is given below:

module Main where

import Prelude

import Effect (Effect)
import Effect.Uncurried (runEffectFn1)
import Partial.Unsafe (unsafePartial)

import Platform as Platform
import Html (Html)
import Html as H
import Css as S
import Attribute as A

type Id = Int

type Model = Array Box
data Msg = Msg_SwapBoxesAndSetText Id String

type Box = { id :: Id , text :: String }

initialModel :: Model
initialModel =
  [ { id: 1, text: "text A" }
  , { id: 2, text: "text B" }
  ]

main :: Effect Unit
main = do
  let app = Platform.app
        { init: \_ -> pure initialModel
        , subscriptions: \_ -> mempty
        , update: \model msg -> pure (update model msg)
        , view: view
        }
  (runEffectFn1 app) unit

update :: Model -> Msg -> Model
update boxes (Msg_SwapBoxesAndSetText targetBoxId newText) =
  boxes # map updateBox # swapOrder
  where
    updateBox box = if box.id == targetBoxId then box { text = newText } else box
    swapOrder = unsafePartial $ \[a, b] -> [b, a]

view :: Model -> { head :: Array (Html Msg), body :: Array (Html Msg) }
view boxes =
  { head: []
  , body:
      [ H.button [ A.onClick $ Msg_SwapBoxesAndSetText 1 "new text" ] [ H.text "swap boxes and set box #1 text to 'new text'" ] ]
      <>
      ( boxes # map \box ->
          H.divS
          [ S.backgroundColor $ if box.id == 1 then "rgba(200 0 0 / 20%)" else "rgba(0 200 0 / 20%)" ]
          [ ]
          [ H.p
            [ ]
            [ H.text $ "Box #" <> show box.id ]
          , H.textarea
            [ A.onInput \text -> Msg_SwapBoxesAndSetText box.id text ]
            [ H.text $ box.text ]
          ]
      )
  }
ursi commented 3 years ago

Well, part of this issue is if you replace the text node in a textarea, it doesn't actually update the textarea's contents image

Quelklef commented 3 years ago

Is this how Elmish is applying the diff?

ursi commented 3 years ago

Yes