question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

dangerouslySetInnerHTML, children and setState quirks

See original GitHub issue

Hello,

first of all, thank you for this great project. After having built several React apps, we’ve decided to give Preact a try. We’ve built an app using preact-cli and almost everything worked flawlessly. I’m really excited about this project!

We ran into one strange issue that took me hours to track down. This is the smallest test case I was able to produce:

import { h, render, Component } from 'preact';
import PropTypes from 'prop-types';

class Container extends Component {

  constructor() {
    super();
    this.state = { state: 'initial' };
  }

  componentDidMount() {
    setTimeout(() => {
      this.setState({ state: 'updated' });
    }, 2000);
  }

  render() {
    const { children } = this.props;
    const { state } = this.state;
    return (
      <div>
        <div>Container state: {state}</div>
        {children}
      </div>
    );
  }

}

Container.propTypes = {
  children: PropTypes.element.isRequired
};

const App = () => (
  <Container>
    <div>App</div>
    <div>
      →
      <span dangerouslySetInnerHTML={{ __html: '<u>HTML</u>' }} />
      ←
    </div>
  </Container>
);

render(<App />, window.document.body);

Expected behavior: After the state change, the content set via dangerouslySetInnerHTML remains unchanged.

Actual behavior: After the state change, the content set via dangerouslySetInnerHTML is removed from the DOM completely.

I’m a newbie to the codebase so I can explain why this happens but I cannot come up with a fix.

Under normal circumstances, when the state of a component changes, the call stack is rerenderidiffinnerDiffNode (which removes the subtree added by dangerouslySetInnerHTML) → diffAttributessetAccessor (which sets innerHTML again).

Under normal circumstances, setAccessor is called because this condition …

attrs[name]!==(name==='value' || name==='checked' ? dom[name] : old[name]))

yields true, in short

attrs[name]!==old[name]

yields true. The old value (an object { __html: '<u>HTML</u>' }) is a equal but not identical to the new value (also an object { __html: '<u>HTML</u>' }). This is because the object is created anew with each App#render call.

In contrast, in the test case above, App#render is not called again when Container re-renders, so it’s the same old attributes object. Therefore diffAttributes does not detect a difference so it does not call setAccessor to set innerHTML again.

Hopefully this analysis from an outsider helps. My guess would be that we need a separate check for name === 'dangerouslySetInnerHTML'. Another possible solution I can think of is to not remove the subtree in the first place, since no change happened.

For reference, here’s the same example based on Facebook’s React: https://codepen.io/anon/pen/BdqVWQ?editors=0010

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Reactions:1
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

10reactions
molilycommented, Aug 29, 2017

Here’s a simple workaround for those who encounter this bug too and need a quick fix. Make sure to not use dangerouslySetInnerHTML in the component that passes children (App in my example) to the component that changes its state (Container in my example). Put it in a child component instead, e.g.:

const InnerHTMLHelper = ({ tagName, html }) =>
  h(tagName, { dangerouslySetInnerHTML: { __html: html } });

const App = () => (
  <Container>
    <div>App</div>
    <div>
      →
      {/* <span dangerouslySetInnerHTML={{ __html: '<u>HTML</u>' }} /> */}
      <InnerHTMLHelper tagName='div' html='<u>HTML</u>' />
      ←
    </div>
  </Container>
);
3reactions
marvinhagemeistercommented, Mar 5, 2019

@molily We just released the alpha for our next version of Preact which doesn’t recreate innerHTML on each render anymore. It checks if the html string has changed before proceeding further, just like you suggested 👍

Read more comments on GitHub >

github_iconTop Results From Across the Web

reactjs - Can only set one of `children` or `props ...
I'm trying to set the inner HTML of an alert div, but receiving the error message : Can only set one of 'children'...
Read more >
preact dangerouslysetinnerhtml - You.com | The AI Search ...
Preact: dangerouslySetInnerHTML, children and setState quirks. Expected behavior: After the state change, the content set via dangerouslySetInnerHTML remains ...
Read more >
Proof of concept React EventListener interface - Plunker
setState message, and delegates on App.prototype! this.setState({count: this.state.count ... dangerouslySetInnerHTML != null) { !(props.children == null) ?
Read more >
Using dangerouslySetInnerHTML in a React application
dangerouslySetInnerHTML is a property that you can use on HTML elements in a React application to programmatically set their content. Instead of ...
Read more >
مدیاویکی:Gadget-experimental-react.js - ویکی‌پدیا، دانشنامهٔ آزاد
There is no guarantee that calls to `setState` will run synchronously, ... ['children', 'dangerouslySetInnerHTML', // TODO: This prevents the assignment of ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found