forgo: Error from orphaned elements trying to render

I’m getting this error in a certain code path is my application:

The rerender() function was called on a node without a parent element.

I can’t manage to create a minimal reproduction for this, and I’ve spent a bunch of hours trying to figure out what’s going on here, even had a friend help debug the forgo source code tonight, but I haven’t figured it out. I’m hoping you have an idea.

It seems that when my component rerenders and returns null, Forgo isn’t correctly updating its bookkeeping. It holds onto the old detached div in args.element, which eventually makes the rerender throw an error. The component’s unmount() method isn’t called in this scenario, either.

The page visually renders fine, but I previously managed to get this error in an infinite loop that locked up page, and since I don’t understand what’s going wrong I can’t just let it go.

I have a parent and a child that both depend on the same forgo-state state, and am using forgo-router.

The application flow that’s happening looks like this: MyComponent onclick -> ajax -> update forgo-state -> MyComponent.render -> queueMicrotask(navigateTo('/app')), return null -> OtherComponent.render -> ajax => update forgo-state -> MyComponent.render

MyComponent shouldn’t get that second render, because the page has navigated away. It should be unmounted. But because it wasn’t, forgo-state tries to rerender MyComponent, but because it’s holding onto the old div (even though the last render returns null) Forgo tries to rerender it and blows up.

(Why queueMicrotask? because forgo-router errors out if the very first render the app does includes an inline navigateTo() call because no components have corresponding DOM nodes yet).

If MyComponent, instead of returning null, returns something like <p>hello</p> I don’t get the error. If I do the navigateTo() immediately in the render rather than in queueMicrotask, I don’t get the error.

I see the error printed several times, if that means anything.

Top of render() in MyComponent:

    render() {
      if (
        authnstate.accessExpiresInHours === null ||
        authnstate.accessExpiresInHours > 0
      ) {
        queueMicrotask(() => navigateTo("/app"));
        //return <p>Redirecting...</p>;
        return null;
      }

The component is declared with bindToStates([authnstate], ...).

MyComponent lives under App, which is also bound to the same state. App contains the router:

             {...
              matchExactUrl(myComponentUrl, () => <MyComponent />) ||
             <p>Welcome!</p>}

Please let me know if I can provide any other info that’ll help. I’m sorry that I can’t trigger this in a minimal sandbox, only in my full application.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (16 by maintainers)

Most upvoted comments

That’s a very good point, I agree. Let’s make this change then.

Sure thing. If you wind up wanting to screen share, it looks like I’ve got Friday morning EST open.