Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hydrate SVG error: Expected server HTML to contain a matching <clipPath> in <svg> #17741

Closed
benbot opened this issue Dec 29, 2019 · 15 comments
Closed

Comments

@benbot
Copy link

benbot commented Dec 29, 2019

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

bug

What is the current behavior?

It seems like ReactDOM.hydrate doesn't play nice with certain kinds of inline SVGs.
Specifically ones that have clipPaths

When nextjs tries calling hydrate on a page with one of these SVGs, the offending part of the svg flashes quickly then react throws this error to the console.

Expected server HTML to contain a matching <clipPath> in <svg>.

I have a very small example of this happening here https://github.com/Delray-Devs/site/tree/brokenSVG

The strange part is when next builds the site statically it all works.

I originally opened this issue on zeit/nextjs, but @timneutkens suggested I open the issue here.

See vercel/next.js#9871 for all the details.

What is the expected behavior?

The SVG containing the clipPath gets rendered correctly on the client side.

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

React: 16.12, not sure about previous versions
Browser: Firefox and Chrome
OS: macOS

@benbot
Copy link
Author

benbot commented Dec 30, 2019

So this is a snippet of the svg that I'm trying to render.

...
<clipPath id="_clip2">
    <path id="sun" d="M533.967 440.216c-4.75-12.501-7.351-26.057-7.351-40.216 0-62.578 50.806-113.384 113.384-113.384S753.384 337.422 753.384 400c0 14.159-2.601 27.715-7.351 40.216H533.967z"></path>
</clipPath>
<g clip-path="url(#_clip2)">
...

it looks like the clip-path attribute on that last g tag has a url call in it. Maybe that's tripping up react?

@davidgolden
Copy link

davidgolden commented Dec 31, 2019

I'm seeing a similar issue, also with Next but it's probably a ReactDOM thing rather than Next. I believe React isn't properly diffing the path element in an SVG. Here's a minimum reproducible example: https://codesandbox.io/s/next-not-updaing-svg-path-xl1u4

In the above example, one would expect the red circle to be rendered on the server but then be updated to the blue square when it's rendered on the client, but that doesn't happen.

Note, there is also a warning in the console: Warning: Prop d did not match.

@benbot
Copy link
Author

benbot commented Jan 2, 2020

Yeah, I bet these are similar problems.
ReactDOM.hydrate seems to have an issue with inline SVGs.

I'm trying to pick through some of the code for hydrate, but honestly I'm having a really hard time following it :(

@benbot
Copy link
Author

benbot commented Jan 3, 2020

actually @davidgolden I think the example you provided there is different. The error makes more sense in your example.

You're trying to render something different during buildtime and runtime, in my case it should be rendering the same thing at both times.

@GasimGasimzada
Copy link
Contributor

GasimGasimzada commented Jan 4, 2020

Expected server HTML to contain a matching in .

I may be mistaken but this line suggests that <clipPath> is missing from the server. Could you please disable JS, open the website, and open the page source (not "Inspect element" but actual page source) to see if clipPath is in the document (you can also do it without disabling JS but it would make it easier to see if the element is visible with the intended markup)?

Btw, I do not have a repo for this but I just created a custom SSR app using webpack, babel, and SVGR loader. Everything seems to be working fine. I am going to try to use babel-plugin-inline-react-svg instead of SVGR loader to see what the output is. I suspect that the problem is in the Babel plugin.

@GasimGasimzada
Copy link
Contributor

I have tested your code in the following scenarios:

  • Build your app and start server in bundle directory
  • Using next command to start the dev server
  • Custom SSR configuration using SVGR Loader
  • Custom SSR configuration using babel-plugin-inline-react-svg Babel plugin w/ no options
  • Custom SSR configuration using babel-plugin-inline-react-svg Babel plugin w/ same options as in the shown repo.

It seems to be working in all the cases. I am not able to reproduce the bug that you have.

Please provide an example where this bug can be reproduced.

@davidgolden
Copy link

@benbot @GasimGasimzada I just cloned your repo and was also unable to reproduce following the steps you provided. However I also don't see a clipPath in your SVG, so maybe you changed something? You're right that we have different issues, sorry about that - I'll open separately.

@benbot
Copy link
Author

benbot commented Jan 7, 2020

@davidgolden @GasimGasimzada Let me run through the steps again. Maybe i knocked something over when i made the branch, but before all i had to do was run next and visit the URL.

Also it's not in the raw svg. The babel-inline-react-svg plugin does some modification and minification of the svg.

@benbot
Copy link
Author

benbot commented Jan 7, 2020

I was able to cause it right away on the brokenSVG branch just by running yarn next. Here's a video of it.

I'll try getting this running on my friend's machine too and see if I can cause this issue on there.

6

@masiucd
Copy link

masiucd commented Jun 26, 2021

I had the same issue and solved it with a hasMounted hook.

 {hasMounted && storedTheme === "dark" ? (
          <Button onClick={handleTheme}>
            <Sun />
          </Button>
        ) : (
          <Button onClick={handleTheme}>
            <Moon />
          </Button>
        )}
import {useEffect, useState} from "react"

const useHasMounted = () => {
  const [hasMounted, setHasMounted] = useState(false)

  useEffect(() => {
    setHasMounted(true)
  }, [])

  return hasMounted
}

export default useHasMounted

@gaearon
Copy link
Collaborator

gaearon commented Mar 30, 2022

The original example is gone so I can't check it.

#17741 (comment) is not a supported way to use React. It is not supported to have conditions like typeof window === "undefined" in your rendering output and render different things on the client and the server. Indeed, #17741 (comment) is a possible workaround. (Though note that your initial HTML render will have the "wrong" icon until JS loads — which is why rendering different things on the server on the client is not supported: it leads to a poor user experience.)

A possible way to fix it is to make server output identical but use CSS to hide/show appropriate icon. Then set the CSS class in an inline <script> before any other HTML based on localStorage.

@gaearon gaearon closed this as completed Mar 30, 2022
@Meid-KH
Copy link

Meid-KH commented Aug 17, 2022

I had the same issue and solved it with a hasMounted hook.

 {hasMounted && storedTheme === "dark" ? (
          <Button onClick={handleTheme}>
            <Sun />
          </Button>
        ) : (
          <Button onClick={handleTheme}>
            <Moon />
          </Button>
        )}
import {useEffect, useState} from "react"

const useHasMounted = () => {
  const [hasMounted, setHasMounted] = useState(false)

  useEffect(() => {
    setHasMounted(true)
  }, [])

  return hasMounted
}

export default useHasMounted

Thanks, man! You saved my a$$

@aguirrealvaro
Copy link

I had the same issue and solved it with a hasMounted hook.

 {hasMounted && storedTheme === "dark" ? (
          <Button onClick={handleTheme}>
            <Sun />
          </Button>
        ) : (
          <Button onClick={handleTheme}>
            <Moon />
          </Button>
        )}
import {useEffect, useState} from "react"

const useHasMounted = () => {
  const [hasMounted, setHasMounted] = useState(false)

  useEffect(() => {
    setHasMounted(true)
  }, [])

  return hasMounted
}

export default useHasMounted

This fixes it, but, the icon will show after the first render, which is not ideal

@mersal-developing
Copy link

good solution but the problem it's render the view after all thing are render which looks weired

@masterial
Copy link

@masiucd good stuff!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants