prismicio / prismic-react

React components and hooks to fetch and present Prismic content
https://prismic.io/docs/technologies/homepage-reactjs
Apache License 2.0
154 stars 40 forks source link

Strange RichText rendering error, stuck trying to debug a clear explanation #116

Closed itsjoekent closed 2 years ago

itsjoekent commented 2 years ago

Hello! I originally posted this on the forum but I was asked to move it here. I'm copying all of the detail from that post to this issue.

Hello! We have been dealing with quite a weird issue when attempting to render a headline using the Prismic RichText React component.

Our product manager (@justduett) noticed that, when an iOS device loaded our website, there was a weird "jump" a few pixels down the page. You can see a video of this here.

Our immediate fix was to simply .scrollTo(0, 0), but I wanted to understand what was actually causing this.

I went through a number of debugging steps to find the culprit, but ultimately I resorted to just deleting entire chunks of the website until the loading bug stopped. Through this brute force method, I was able to find the single header on the site responsible for this bug.

image

This header (which uses a style and font in numerous other places higher up the page) is rendered by the Prismic RichText component. Now, my first instinct was to investigate the CSS, animations, and other surrounding markup. However, no matter what I tried getting rid of, the bug persisted.

So then I tried something silly. I wrote pure HTML to match what the RichText was outputting. And the bug went away.

<h2>Features developers love.<br/>Experiences <br/>customers trust.</h2>
<RichText render={data.primary.section_title}></RichText>

And here is the HTML comparison, they're identical.

image

image

For reference, here is the data supplied to the RichText prop,

[
  {
    "type": "heading2",
    "text": "Features developers love.\nExperiences \ncustomers trust.",
    "spans": []
  }
]

This seemed really strange to me. Why is the same HTML markup working if I just don't render it with Prismic's RichText component? Surely Prismic isn't calling .scrollTo anywhere, and we use this same code pattern in numerous places on this page, why would only this title be having this issue?

So I tried a number of things, like removing the line breaks that got passed into the RichText component, and it made no difference. The only change I could make to "fix" the RichText component was to either make it 1 word, or reduce the font size to something very small. Obviously neither of these are "fixes" and I don't quite understand why they worked, but I think it might help provide useful context as folks try to wrap their head around this.

So, we were on an old version of Prismic's React library, 1.3.4, and I thought it was worth investigating if there may in fact be this unexplainable bug in Prismic's library. So I pulled the library down, npm link'd it to our application, and started debugging.

I wrapped my debug code with the following conditional, so it only ran for the specific title we're investigating here,

  if (render && JSON.stringify(render).includes('Features')) {

Within that conditional, I returned the following output,

const output = React.createElement('h2', null, [
  'Features developers love.',
  React.createElement('br'),
  'Experiences',
  React.createElement('br'),
  'customers trust.',
]);

Yet again, same expected markup as before, and the bug went away. Wat

So now I started comparing the output of what Prismic was generating with the React API, to what I would expect it to generate with something like what I wrote above.

What I found is that, Prismic was nesting elements in an array within another array,

        "props": {
          "children": [
            [
              "Features developers love.",

This seemed wrong, and while I didn't understand entirely how it contributed to the literal scroll issue I started investigating (maybe this created some weird rendering "blip" with React?), I figured this might be a sign we should bite the bullet and upgrade Prismic.

Well, one huge Prismic upgrade later, the issue is still there. And yet again, if I npm link the latest version of @prismicio/react to our website, and inject the following code into the useMemo hook of PrismicRichText,

if (props.debug) {
   return (
      <h2>
        Features developers love.
        <br />
        Experiences <br />
        customers trust.
      </h2>

The iOS scroll problem entirely goes away. The problem only occurs if I return the serialized variable (which seems to stem from a mix of this repo & the react package). The serialized variable literally matches the same HTML I wrote above.

I have hit a wall trying to figure this one out. I realize this is extremely hard to debug, and I would be happy to provide any additional information that could be helpful.


I also want to note, if you attempt going to our website to see for yourself,

Versions

angeloashmore commented 2 years ago

Hey @itsjoekent, thank you so much for the very detailed triaging and description. This is definitely a strange bug. Thankfully I have an iPhone to test this. 😄

I'll be looking into this today and will post here once I understand the problem better.

itsjoekent commented 2 years ago

@angeloashmore Thank you @angeloashmore , let me know if you need my help trying to create a reproducible example. I have an idea in my head of how I would do this that wouldn't require sending you our entire website, would just need to clear with my team spending some time on that.

angeloashmore commented 2 years ago

@itsjoekent Unfortunately, I am having trouble creating a reproduction of this issue. Is it possible you could create a project with just that section of the website? Ideally you could share the version that has been upgraded to @prismicio/react.

If you would like to share the code privately, you can send me a message on Prismic's forum with this link: https://community.prismic.io/new-message?username=angeloashmore&title=prismic-react%20Issue%20%23116

A ZIP file of the project is sufficient, or you can share it in any other way you see fit.

Thank you again!

itsjoekent commented 2 years ago

@angeloashmore Working on getting internal permission to share code, will get back to you!

itsjoekent commented 2 years ago

@angeloashmore Sent!

angeloashmore commented 2 years ago

Hey @itsjoekent, just letting you know I didn't forget about this. I have time today to test out your reproduction and hopefully find what's causing the issue. Thanks for your patience!

angeloashmore commented 2 years ago

@itsjoekent This was fun to debug! I think I figured out the issue and how to fix it. If I'm correct, it's not caused by <PrismicRichText>. 🙂

Why does the issue happen?

The issue happens because the tt-bluescreens font is not available initially. This is why the issue only occurs on an empty cache. Something could be calculating something about the canvas and changing the scroll position before the font is available (are there any scrollTos?).

Before the font is loaded, headings use the fallback sans-serif font. This font is much wider than tt-bluescreens. Long, non-wrapping words cause the text to overflow outside the Slice’s bounding box. The viewport is configured to shrink the page’s canvas to fit within the viewport’s width. This ultimately messes with the page’s canvas size during scroll position calculation (if that’s what the site is doing).

As you know, the large layout shift was caused by the one_picture_section Slice. This is a result of its heading content, not because of its use of <PrismicRichText>. If you were to replace the heading content with short words, like "Hello World," the issue would likely not occur.

If you want to see what I'm talking about re: overflow, remove tt-bluescreens from the fonts.heading theme value. On an iOS device, the heading will extend far outside the Slice's container.

Why did replacing PrismicRichText with HTML seem to fix the issue?

The replacement HTML you tested was actually not identical to what was rendered from Prismic.

<h2>Features developers love.<br/>Experiences <br/>customers trust.</h2>

There should be an &nbsp; between “developers” and “love”. You can confirm this by inspecting the HTML when rendering with <PrismicRichText>.

With the correct content in place, the issue occurs exactly as with <PrismicRichText>.

<h2>Features developers&nbsp;love.<br/>Experiences <br/>customers trust.</h2>

This difference is significant because &nbsp; is a “non-breaking” space. This means the browser needs to render the string “developers love” with no line breaks. Combined with wide width of sans-serif (before tt-bluescreens is loaded), this causes the Slice component to overflow significantly.

Possible fixes


I'm pretty sure this is what's happening. If it still seems to be directly related to <PrismicRichText>, please let me know and I will take another look.

itsjoekent commented 2 years ago

@angeloashmore This all makes sense! I ruled the font loading out as a possibility when (I thought*) I had evidence that it was only occurring when we rendered with the rich text component.

I am curious for future reference, how were you able to locate/identify the &nbsp; symbol? I don't recall coming across that in the Chrome inspect elements tab or the React output.

And thank you for the incredible write-up & looking into this! Sorry it turned out to be a false report.

angeloashmore commented 2 years ago

@itsjoekent No problem! I'm glad we were able to figure out the problem. 🙂

Re: identifying the &nbsp; - I inspected the <h2> in Chrome and noticed the extra character in there. It's definitely not obvious when looking at the rendered version of it.