react icon indicating copy to clipboard operation
react copied to clipboard

Unexpected warning when hydrating with portal and SSR

Open majelbstoat opened this issue 6 years ago • 27 comments

Do you want to request a feature or report a bug?

bug

What is the current behavior?

Given the following (simplified) snippet:

class HoverMenu extends React.Component {
  render() {
    if (typeof document === 'undefined') return null
    const root = document.getElementById('root')
    return ReactDOM.createPortal(<div>Hello World</div>, root)
  }
}

class Para extends React.Component {
  render() {
    return (
      <span>
        Some Text
        <HoverMenu />
      </span>
    )
  }
} 

where div#root is a valid div that exists, the following error is shown when hydrating after SSR:

Warning: Expected server HTML to contain a matching <div> in <span>

The warning goes away if I update the definition of HoverMenu to:

class HoverMenu extends React.Component {
  componentDidMount() {
    this.setState({ isActive: true })
  }
  render() {
    const { isActive} = this.state
    if (!isActive) return null
    const root = document.getElementById('root')
    return ReactDOM.createPortal(<div>Hello World</div>, root)
  }
}

I'd prefer not to do that because of the double rendering caused by setState in componentDidMount.

I don't quite understand what that error is telling me. No <div /> is rendered server-side in either case. The error is particularly confusing, as the HoverMenu DOM div is not even rendered inside a DOM span. (I wonder if this is happening because HoverMenu is nested inside a React span.)

What is the expected behavior?

No error is thrown. Or, at least that the error message is clearer.

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

Chrome 65 React 16.2 (SSR through Next 5.1)

majelbstoat avatar Apr 15 '18 07:04 majelbstoat

I have a similar issue, which can also be solved by re-rendering on the client via setState. In my case, I try to render a modal inside a portal. The Modal component returns null when rendered on the server and creates a portal on the client. However, the DOM gets messed up after hydration.

E.g. if I use it like this inside my main component:

<Modal>
This is a test
</Modal>

<div className="some-div-after-the-modal">
</div>

Instead of getting this after hydration:

<!-- Inside the portal container -->
<div class="modal-wrapper">
    <div class="modal-content">This is a test</div>
</div>

<!-- In the main component -->
<div class="some-div-after-the-modal">
</div>

I get this:

<!-- Inside the portal container -->
<div class="some-div-after-the-modal">
    <div class="modal-content">This is a test</div>
</div>

<!-- In the main component -->
<div class="some-div-after-the-modal">
</div>

And the warning is the same (Expected server HTML to contain a matching <div> in <div>). I use React 16.3 with a custom SSR method.

I'm not sure if this is the intended behavior.

interphx avatar Apr 16 '18 11:04 interphx

While hydrating portals is not supported (https://github.com/facebook/react/issues/13097), the message itself doesn't make sense. We'll need to investigate and fix it.

gaearon avatar Aug 02 '18 19:08 gaearon

@gaearon The purpose of fix is

  • [ ] Detects Portal is being used in hydrate() and then throw more properly error message by invariant. e.g. invariant violation: Portal is not support on SSR. For more detail, please refer https://github.com/facebook/react/issues/13097
  • [ ] Add Test
    • [ ] If the Portal is used with the hydrate(), should throw the above error message

right? I'm willing if it isn't urgent.

I'm planning SSR Doc draft to website repo, so I have to be familiar to hydrate mechanism. I think this investigation will be helpful for my plan. https://github.com/facebook/react/pull/13379

ryota-murakami avatar Aug 30 '18 05:08 ryota-murakami

Why are Portals not supported on SSR, even to render "nothing"?

ljharb avatar Aug 30 '18 05:08 ljharb

It doesn’t seem like rendering nothing is best — I don’t see what makes portal contents different that we don’t consider it worth server rendering.

We’ll likely come back to this together with the revamp of server renderer for suspense.

gaearon avatar Aug 30 '18 10:08 gaearon

Portals are conceptually client-only components; used for things like modals that one generally wouldn’t want to render on the server - they also take a dom element, which renderToString doesn’t because there is no dom. I don’t see how there’s anything meaningful to be done with them on the server - i just also don’t see anything valuable from throwing.

ljharb avatar Aug 30 '18 14:08 ljharb

used for things like modals

That's one use case but there are also others like sidebars and similar which are not necessarily client-only.

they also take a dom element

Right — in the current design. It could change. https://github.com/facebook/react/pull/8386#issuecomment-262375265

i just also don’t see anything valuable from throwing.

The value of throwing is to explicitly acknowledge that portal won't work. You can easily work around it with {domNode && ReactDOM.createPortal(stuff, domNode)} or similar. Because you already had to do some kind of checking to determine whether you can get the DOM node — so at this point you should have enough data to choose not to emit the portal.

gaearon avatar Aug 30 '18 14:08 gaearon

I would like to fix this as my "good first issue"

brandonvilla21 avatar Sep 09 '18 23:09 brandonvilla21

+1

lvguoying avatar Oct 03 '18 06:10 lvguoying

Can I work on this issue?

joseantonjr avatar Oct 06 '18 14:10 joseantonjr

Sure. I guess you can. So how far have you gone? I'm actually thinking of picking it up too.

engprodigy avatar Oct 23 '18 11:10 engprodigy

  • 2

yjang4 avatar Oct 28 '18 00:10 yjang4

Hi folks, any idea how I can find the file where the issue is, I can get it please some help.

ELBEQQAL94 avatar Apr 20 '19 13:04 ELBEQQAL94

Hey here is the partial pull request for this issue https://github.com/facebook/react/pull/15473

VariableVasasMT avatar Apr 23 '19 13:04 VariableVasasMT

Anyone still on this? I'd love to take it on.

nileshgulia1 avatar May 18 '19 16:05 nileshgulia1

What's the status of this? How could I reproduce it?

j-stuckey avatar May 20 '19 20:05 j-stuckey

Is this still an issue?

dhulwells avatar Jun 15 '19 01:06 dhulwells

I have not gotten a response on this issue either.

j-stuckey avatar Jun 15 '19 23:06 j-stuckey

I believe the status of this issue is still open.

MuraliSRao avatar Feb 10 '20 08:02 MuraliSRao

Can I take on this issue?

JeremiahKamama avatar Feb 16 '20 13:02 JeremiahKamama

Hello, is anyone working on this issue? If not I would like to take it.

akolbuszewski avatar Apr 22 '20 09:04 akolbuszewski

if no one is working on it, I can

DiasKazhtai avatar May 02 '20 11:05 DiasKazhtai

Is this still an issue? It seems like attempts were made to fix it, but the underlying problem is that some people actually want portals to work on server-side rendering? If that's the case can this issue be closed?

JoaoVitoGomes avatar Jun 15 '20 18:06 JoaoVitoGomes

Adding a div element should work fine

`class Para extends React.Component { render() { return (

Some Text
) } } `

nikikalwar avatar Sep 25 '20 23:09 nikikalwar

My sidebar appears on the client after everything else, this flash is ugly.

bertho-zero avatar Jun 04 '21 10:06 bertho-zero

please let me know some good first issues that I can work on as I am new to open source

dev-amara avatar Feb 11 '22 20:02 dev-amara

const [isClientSide, setIsClientSide] = useState(false);

  useEffect(() => {
    // this useEffect prevents showing Hydration Error
    if (!isClientSide) {
      setIsClientSide(true);
    }
  }, []);
.
.
.

return (
    <>
      {typeof document !== "undefined" &&
        isClientSide &&
        createPortal(
          <div>
            -- Your JSX code
          </div>,
          document.body
        )}
    </>
  );


komailf67 avatar Oct 23 '22 08:10 komailf67

Old issue but if someone sees it...

I use the below hook to render the contents of the portal or other components if SSR would somehow fail to render it correctly, use sparingly, SSR is a good thing.

"use client";
import { useEffect, useState } from "react";

export const useIsClient = () => {
    const [isClient, setIsClient] = useState(false);

    useEffect(() => {
        setIsClient(true);
    }, []);

    return isClient;
};

...
const isClient = useIsClient();
const retval =
    isClient &&
    <>
        <Portal>
            {......}
        </Portal>

    </>
return retval
...

cemcakirlar avatar Jan 20 '24 17:01 cemcakirlar