React: Have React ignore a specific DOM element

Created on 26 Apr 2016  路  11Comments  路  Source: facebook/react

As per my chat here with @gaearon I'm filing an issue to discuss this further.

I would like to avoid a specific element that I rendered on the server from beeing further updated by react once it reaches the client.
One specific use case is rendering ad server tags that are kind of a pain in the ars, using things like document.write, etc. I render them using dangerouslySetInnerHTML but sometimes when React is doing the reconciliation client side they get re-render so they stop executing. Now I managed to track down some of these cases by fixing render differences between server and client which would trigger DOM patching but it still seems to happen.
Any advice?

DOM Server Rendering Feature Request

Most helpful comment

@gaearon I have a use case.

I want to render a big component (that represents a page), but there are a couple of id's within that page that we inject into from a 3rd party. We can obviously break up this page component into like 6 different disparate components we inject separately, but it's a much worse experience.

<div class="container">
  <div id="ad1"></div>
  <h1>Hello world! Welcome to the page</h1>
  <div id="ad2"></div>
</div>

So essentially the server pre-renders the react and gives this to us, and then 3rd party javascript executes, putting ads in the ad divs. Then React hydrates, and blows the ads away. It would be nice to be able to mark the divs with some sort of "do not update" sort of flag and ignore their differences. I want the ability to manage all my html in React, and have it control that first server side render, but I just want it to ignore some spots on client render.... almost like a shouldComponentUpdate() { noIfClient }

All 11 comments

cc @aickin who is experimenting with changing how server markup is validated in #6618

@nickdima I would recommend figuring out why the DOM differs. You should be fixing those errors anyway, and that's the "theoretically correct" solution.

When validation fails, there is the question of "what todo". React needs to be able to update the DOM (that's its job, after all), but if there are arbitrarily wrong nodes in the render tree, it's not obvious how updates should occur. In such situations, React just blows away the entire render tree and starts from scratch, thus ensuring that everything matches. This has the side effect of clearing any DOM state (for better or for worse).

I think Sebastian likes the current behavior (always blow away the entire tree if validation fails) because it avoids "interaction bugs". Suppose a user clicks the "delete" button for the third document in a list. When the element was rendered with SSR, the document had the title "old todo list", but when rendered on the client side the third item is a document entitled "important financial/tax documents". React notices that only the element title changes, so it applies the update and then fires the click listener for the "delete" operation. Now the user just deleted the wrong document. Throwing away the entire DOM tree minimizes the likelihood of this happening, because everything (including pending operations/state) is destroyed.

Personally, I don't have an opinion. cc @sebmarkbage, who may have an opinion.

It is not always possible to fix DOM diffing. E.g. internationalized date formats can't be done the same way on the server and client for certain cases because the Windows internationalization libraries differ and are closed source.

It seems strange that your ad code would differ though.

Well, most ad code appends elements to the div in which is inserted so could it be that when the client code boots up that div was already manipulated so React tries to bring it back to what the render function generates?

That's not how it works right now (before #6618). It diffs against the checksum, not the actual DOM.

OK, then it's something else. BTW, we're on react 0.13.
We have no diff warnings any more. Any other way to make sure there are no differences?

This feature is needed to render non-react code. For example we need to render some 3rd party js code. Now we do something like this

export default class ThirdParty extends React.Component {
  componentDidMount() {
    const rootEl = this._rootEl;
    // init 3rd party code here that inserts its own html inside rootEl
  };

  shouldComponentUpdate() {
    return false;
  };

  componentWillUnmount() {
    // destroy 3rd party code here
  };

  render() {
    return (
      <div ref={el => { this._rootEl = el; }}></div>
    );
  }
};

On the documents page we can read the following:

Note that in the future React may treat shouldComponentUpdate() as a hint rather than a strict directive, and returning false may still result in a re-rendering of the component.

If this happens, then the code above will be broken for sure.

In React 16 we now actually diff against the DOM.
Since 16.1.0, we also provide an attribute to mark intentional differences.

https://reactjs.org/docs/dom-elements.html#suppresshydrationwarning

If you use server-side React rendering, normally there is a warning when the server and the client render different content. However, in some rare cases, it is very hard or impossible to guarantee an exact match. For example, timestamps are expected to differ on the server and on the client.

If you set suppressHydrationWarning to true, React will not warn you about mismatches in the attributes and the content of that element. It only works one level deep, and is intended to be used as an escape hatch. Don鈥檛 overuse it. You can read more about hydration in the ReactDOM.hydrate() documentation.

That鈥檚 a bit different from what you鈥檙e describing though. It鈥檚 hard to answer your specific question without seeing a very small example that reproduces exactly the case you are seeing. I鈥檒l close this but let me know if it鈥檚 still relevant.

@gaearon I have a use case.

I want to render a big component (that represents a page), but there are a couple of id's within that page that we inject into from a 3rd party. We can obviously break up this page component into like 6 different disparate components we inject separately, but it's a much worse experience.

<div class="container">
  <div id="ad1"></div>
  <h1>Hello world! Welcome to the page</h1>
  <div id="ad2"></div>
</div>

So essentially the server pre-renders the react and gives this to us, and then 3rd party javascript executes, putting ads in the ad divs. Then React hydrates, and blows the ads away. It would be nice to be able to mark the divs with some sort of "do not update" sort of flag and ignore their differences. I want the ability to manage all my html in React, and have it control that first server side render, but I just want it to ignore some spots on client render.... almost like a shouldComponentUpdate() { noIfClient }

Same here, our current system wants to inject 3rd party element which is done on the server but react cleans it when loaded, an example as @gaearon requested

Client

const ThirdPartyInjector = () => <div>{'{{3rd_party_widget}}'}</div>;

Server

let html = renderToStaticMarkup(<ThirdPartyInjector />);
html = html.replace('{{3rd_party_widget}}', get3rdPartyWidgetDomElement());

This is obviously a very bad hack that some of us had to do when integrating react into a legacy system, but as @reywright mentioned maybe react can introduce a shouldComponentMount() that we can enable on the server but not on frontend ?

Btw, currently we found a scary workaround, I'll share maybe someone will find it helpful

const ThirdPartyInjector = () => {
  if (__IS_SERVER_SIDE__) {
    return (
      <Helmet
        script={[
          {
            type: 'text/javascript',
            innerHTML: "window['3RD_PARTY_WIDGET'] = `{{3rd_party_widget}}`;"
          }
        ]}
      />
    );
  }

  return (
    <div dangerouslySetInnerHTML={{ __html: window['3RD_PARTY_WIDGET'] }} />
  );
};

Edit: Since React 16 like gaeron suggested react will not try to change what the server renders so we just needed to update react version

I'm having the same issue and the docs recommend an empty div with a ref:

class SomePlugin extends React.Component {
  componentDidMount() {
    this.$el = $(this.el);
    this.$el.somePlugin();
  }

  componentWillUnmount() {
    this.$el.somePlugin('destroy');
  }

  render() {
    return <div ref={el => this.el = el} />;
  }
}

To prevent React from touching the DOM after mounting, we will return an empty

from the render() method. The
element has no properties or children, so React has no reason to update it, leaving the jQuery plugin free to manage that part of the DOM

but this doesn't seem to prevent rerenders, even with shouldComponentUpdate false

Was this page helpful?
0 / 5 - 0 ratings