react
react copied to clipboard
Unexpected warning when hydrating with portal and SSR
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)
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.
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 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
Why are Portals not supported on SSR, even to render "nothing"?
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.
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.
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.
I would like to fix this as my "good first issue"
+1
Can I work on this issue?
Sure. I guess you can. So how far have you gone? I'm actually thinking of picking it up too.
- 2
Hi folks, any idea how I can find the file where the issue is, I can get it please some help.
Hey here is the partial pull request for this issue https://github.com/facebook/react/pull/15473
Anyone still on this? I'd love to take it on.
What's the status of this? How could I reproduce it?
Is this still an issue?
I have not gotten a response on this issue either.
I believe the status of this issue is still open.
Can I take on this issue?
Hello, is anyone working on this issue? If not I would like to take it.
if no one is working on it, I can
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?
Adding a div element should work fine
`class Para extends React.Component { render() { return (
My sidebar appears on the client after everything else, this flash is ugly.
please let me know some good first issues that I can work on as I am new to open source
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
)}
</>
);
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
...