Preact: Rendering components with identical children into the same parent container breaks

Created on 16 Apr 2016  路  7Comments  路  Source: preactjs/preact

If you try to render two different components into the same parent container multiple times with shared child components the browser will throw an error:

Uncaught HierarchyRequestError: Failed to execute 'replaceChild' on 'Node': The new child element contains the parent.

"preact": "4.6.2",
"preact-render-to-string": "2.4.0"

Chrome Version 50.0.2661.75 (64-bit)

This is easier to explain with some code:

class Intro extends Component {
  render(props) {
    return (<span>Hello</span>)
  }
}
class Index extends Component {
  render(props) {
    return (
      <div>
        <Intro />
      </div>)
  }
}
class Join extends Component {
  render(props) {
    return (<Intro />)
  }
}
  let components = [Index, Join, Index, Join, Index]
  // This loop simulates the user navigating forward and backwards over two components.
  components.forEach(function(Cmp) {
    render(
      <Cmp data={window.preactProps} />,
      document.body,
      document.body.firstChild)
  })
bug important

All 7 comments

Ah - looks like this has to do with one of the components being high-order and the other not.

@hatched Quick fix for this (at least for your example) would be to not rely on document.body.firstChild. (interesting to note: using document.body.lastChild seems to fix the issue above).

Instead, I'd recommend using the returned root node from render():

  let root;
  let components = [Index, Join, Index, Join, Index]
  // This loop simulates the user navigating forward and backwards over two components.
  components.forEach(function(Cmp) {
    root = render(
      <Cmp data={window.preactProps} />,
      document.body,
      root)
  })

This accounts for the case where render() destroys the previous rendered root.

I'm working on a lower-level fix for this, in case the explanation above only solves your reproduction and not the bug as you ran into it (let me know).

Thanks for the quick reply!

Because this is being first rendered on the server I had to initialize root with document.body.firstChild so that it wouldn't get a duplicate render on load. With that change it appears to have resolved the issue.

function renderParent(Component) {
  let root = document.body.firstChild
  root = render(
    <Component data={window.preactProps} />,
    document.body,
    root)
}

Perfect. Now I'm a little stuck: should I keep the fix I just added that checks before invoking replaceChild(), or drop it since it still doesn't fix the firstChild issue?

I think that it is always a good idea to code defensively and it is a quite small change so unless there are some unintended consequences that I'm not aware of I'd vote to keep it in.

True, it's literally 2 bytes after gzip. I shouldn't agonize over these things so much, haha.

I'm going to close this issue out for now since we've got a solution, though I'll likely re-reference it when I address making render() automatically detect and attach to existing rendered components.

_edit:_ for reference, reproduction I was using to test: http://jsfiddle.net/developit/t8p15pps/

Was this page helpful?
0 / 5 - 0 ratings

Related issues

matuscongrady picture matuscongrady  路  3Comments

kossnocorp picture kossnocorp  路  3Comments

Zashy picture Zashy  路  3Comments

KnisterPeter picture KnisterPeter  路  3Comments

jasongerbes picture jasongerbes  路  3Comments