Content-Length: 313477 | pFad | https://github.com/purescript-halogen/purescript-halogen/issues/802

68 Child component gets rendered outside of parent, being moved to bottom of HTML body · Issue #802 · purescript-halogen/purescript-halogen · GitHub
Skip to content

Child component gets rendered outside of parent, being moved to bottom of HTML body #802

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
abigailalice opened this issue Jun 17, 2022 · 6 comments · May be fixed by #820
Open

Child component gets rendered outside of parent, being moved to bottom of HTML body #802

abigailalice opened this issue Jun 17, 2022 · 6 comments · May be fixed by #820

Comments

@abigailalice
Copy link

I've been experiencing some bizarre non-deterministic behaviour in an app I'm writing, which I think might be a Halogen issue. Basically I've got a child component which occasionally somehow gets moved to the very bottom of the HTML body, outside of its parent. Unless my mental model of how components are supposed to work is completely wrong I can't see how this could ever happen in Halogen or in the code I've written, but trying to isolate the issue I think I've narrowed it down to a higher-order component I wrote like:

premap
    :: forall a b o m query
     . Monad m
    => (forall i r. a -> HTML i r)
    -> (a -> m b)
    -> Component query b o m
    -> Component query a o m
premap html f h = mkComponent
    { initialState: Left
    , render: case _ of
        Left a -> html a
        Right a -> slot _nil unit h a Raise
    , eval: mkEval defaultEval
        { handleAction = handleAction
        , handleQuery = handleQuery
        , receive = Just <<< Receive
        , initialize = Just Initialize
        }
    }
  where
    handleAction (Raise o) = raise o
    handleAction (Receive i) = do
        b <- lift (f i)
        put (Right b)
    handleAction Initialize = do
        s <- get
        case s of
            Left a -> do
                b <- lift (f a)
                put (Right b)
            _ -> pure unit
    handleQuery :: forall a. query a -> HalogenM _ _ _ _ m (Maybe a)
    handleQuery f = request _nil unit (const f)

I'm wondering if there are any issues using higher-order components like this? I've used this combinator in a few places with no issues up until now, but it only started occurring once I introduced a new component that happens to be built using this; removing the call to premap seems to remove the bug. The call to premap is nested inside another component which is also built using it, and introducing the child component detaches things starting from the first parent which also uses premap, so I start up with something like this:

<grandparent>
    <parent>
         <child></child>
    </parent>
</grandparent>

And end up with

<grandparent></grandparent>
<parent>
    <child></child>
</parent>

Because of the non-determinism it's hard to be sure that I've absolutely found the cause of the issue, but I can usually manually trigger the issue in less than a minute when the new component is wrapped in the call to premap, even if the call is just premap (\_ -> HH.text "") pure. I haven't managed to trigger it without it.

I've even managed to detach the component, click around the app so that a new component gets rendered correctly inside the grandparent, then detach that one as well, ending up with multiple components being rendered outside of the grandparent. This is without using integers or anything for the keys to slots, just proxies, which I assumed was necessary for Halogen to manage state internally, so I should think they would all need to share state, but once they get detached they don't seem to.

I'm really perplexed by the behaviour in any case. Inspecting the html and looking at the render functions together makes me think what is occurring shouldn't be possible.

@farzadbekran
Copy link

farzadbekran commented Jul 21, 2022

I think I had a similar issue to this, where I was rendering one of two slot components, based on some input to the parent component. Something like this:

  render :: State -> H.ComponentHTML Action ChildSlots m
  render { loginType } =
    case loginType of
      Just (ULC.UL _) -> HH.slot (Proxy :: _ "passwordSlot") unit Pass.form
        unit
        HandlePass
      Just (ULC.Cell cell) -> HH.slot (Proxy :: _ "verificationSlot") unit Ver.form
        cell
        HandleCode
      Nothing -> HH.slot (Proxy :: _ "userloginOrCellSlot") unit ULC.form unit
        HandleULOrCell

which after some interactions and state changes in the parent, which caused the rendered child slot to switch, if I changed the route, (from login page to home page for example), the new page's HTML would be added to the document, but the child slot from the old page would not be removed. I solved it by wrapping the child slots inside a div like this:

  render { loginType } =
    HH.div_
      [ case loginType of
          Just (ULC.UL _) -> HH.slot (Proxy :: _ "passwordSlot") unit Pass.form
            unit
            HandlePass
          Just (ULC.Cell cell) -> HH.slot (Proxy :: _ "verificationSlot") unit
            Ver.form
            cell
            HandleCode
          Nothing -> HH.slot (Proxy :: _ "userloginOrCellSlot") unit ULC.form
            unit
            HandleULOrCell
      ]

My guess is that when a component is to be removed from the document, halogen's diffing algorithm somehow looks for the root node of the component which is to be removed and since it has changed completely (not the same root when it was mounted), it can't find it and the child components remain in the DOM.

So I suggest you wrap your rendered slots inside a div and see if it changes anything for you:

render: \state -> HH.div_ 
  [case state of
      Left a -> html a
      Right a -> slot _nil unit h a Raise]

Not sure if my situation is the same as yours, but I think it's worth a try. Let me know if it works, because I'm not sure if my thinking is correct. If it is, I think it would count as a bug.

@garyb garyb added the bug label Jul 21, 2022
@garyb
Copy link
Member

garyb commented Jul 21, 2022

It's definitely a bug! I thought I'd tagged this issue as such, or that possibly it's a duplicate of another actually, as I've definitely heard about this problem before (using a slot directly inside render causing problems). I think about it quite often, but haven't gotten around to dedicating some time to figuring out how exactly it's going wrong.

@thomashoneyman
Copy link
Member

thomashoneyman commented Jul 21, 2022

as I've definitely heard about this problem before (using a slot directly inside render causing problems).

It comes up somewhat often — for example, just a few days ago:
thomashoneyman/purescript-halogen-store#17

(the halogen-store connect component is a higher-order component that just renders a slot directly)

And perhaps it relates to this?
#748

@garyb
Copy link
Member

garyb commented Jul 21, 2022

Thanks for the links 👍, I knew it definitely happened in relation to -store as that's where I remember hearing about it first, but you could well be right that it's related to the other thing with memoized too.

@abigailalice
Copy link
Author

I tried wrapping my render functions in a div like the workaround suggested, and it does seem like that fixed it. Thanks.

@garyb
Copy link
Member

garyb commented Oct 3, 2022

The other issues that are probably this are #766 and #657

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/purescript-halogen/purescript-halogen/issues/802

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy