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

useColorMode() cannot safely be used with SSR #9629

Closed
2 of 7 tasks
NickGerleman opened this issue Dec 14, 2023 · 5 comments
Closed
2 of 7 tasks

useColorMode() cannot safely be used with SSR #9629

NickGerleman opened this issue Dec 14, 2023 · 5 comments
Labels
bug An error in the Docusaurus core causing instability or issues with its execution closed: duplicate This issue or pull request already exists in another issue or pull request

Comments

@NickGerleman
Copy link
Contributor

Have you read the Contributing Guidelines on issues?

Prerequisites

  • I'm using the latest version of Docusaurus.
  • I have tried the npm run clear or yarn clear command.
  • I have tried rm -rf node_modules yarn.lock package-lock.json and re-installing packages.
  • I have tried creating a repro with https://new.docusaurus.io.
  • I have read the console error message carefully (if applicable).

Description

I have a website which relies on the usePrismTheme() hook, relying on useColorMode(). The website has an interactive code example (very similar in both form and code to the one provided by Docusaurus itself).

The site is dark mode by default, but may be set to light mode. When I start the website, after previously setting light mode, everything looks corect when I start via development server, but if I do a build, the code relying on the usePrismTheme() hook shows the incorrect theme.

image image

When I debugged this, it looks like:

  1. Docusaurus SSR generates the page using a color mode of "dark"
  2. useColorMode, from first client render, has a color mode of "light"
  3. Hydration succeeds, even though the prism styles are mismatched?
  4. We're in a weird hybrid state where the color is incorrect until the theme is changed again

I found Docusaurus itself has a hack to get around this:

But this causes a black to white (or white to black) flash during hydration which is pretty jarring if the UI is shown above the fold.

This seems like a more general issue, where anything relying on the hook might get a different value at client time compared to server time. Though I'm not exactly sure how this could be cleanly solved without doing SSR on both sets of color modes.

Reproducible demo

https://github.com/facebook/yoga/tree/main/website-next

Steps to reproduce

  1. yarn start (works correctly)
  2. yarn build (does not show correctly)

Expected behavior

Light mode should use light scheme in prism editor

Actual behavior

Shows dark theme

Your environment

Self-service

  • I'd be willing to fix this bug myself.
@NickGerleman NickGerleman added bug An error in the Docusaurus core causing instability or issues with its execution status: needs triage This issue has not been triaged by maintainers labels Dec 14, 2023
@NickGerleman
Copy link
Contributor Author

NickGerleman commented Dec 14, 2023

Oh I probably should have searched before I made this. Looks like #7986 is effectively the same thing.

@NickGerleman
Copy link
Contributor Author

NickGerleman commented Dec 14, 2023

For this specific case of issue of code highlighting, I bet I could make a custom prism theme that uses variables set by style with html data theme selector like other responsive UI does.

@slorber
Copy link
Collaborator

slorber commented Dec 14, 2023

Yes this is a known issue.

At build time we have to pick one color mode to render the static pages, and cannot read OS settings or localStorage there so we have to pick one or another.

At hydration time we should hydrate with the exact same color that was used at build time (I don't think we do this correctly atm), and only then update the color in React state to match the actual rendered value.

We inline critical JS in the page to render the correct color mode through CSS selectors, before the hydration, this is what prevents our theme to have a flash. Unfortunately, React has to see the real color mode a bit later, after hydration happens.

My recommendations would be:

  • Prefer CSS selectors for theming ([data-theme='dark'] { ... }) to avoid flashes
  • Reduce usage of useColorMode() to limited use-cases, like a color mode toggle button

Regarding the prism code block fouc, we'd also want to use CSS variables for this. See FormidableLabs/prism-react-renderer#149

I guess one issue is enough, so let's close in favor of the existing one: #7986

@slorber slorber closed this as not planned Won't fix, can't repro, duplicate, stale Dec 14, 2023
@slorber slorber added closed: duplicate This issue or pull request already exists in another issue or pull request and removed status: needs triage This issue has not been triaged by maintainers labels Dec 14, 2023
NickGerleman added a commit to NickGerleman/yoga that referenced this issue Dec 14, 2023
Summary:
See facebook/docusaurus#9629

We use prism to render the code for the inline editor. Prism renders colors to style directly, and the color chosen is dendent on a setting that may not be available at SSR time.

This adds an SSR-specific representation of the code, missing some of the nuances in token colorization. This adds a little bit of jank compared to perfect SSR, but fixs cases where the mode is incorrect, and is a lot less jank then the more generic solution used by theme-live-codeblock of keeping the rendering of the opposite color until rehydration.

Differential Revision: D52163722
facebook-github-bot pushed a commit to facebook/yoga that referenced this issue Dec 19, 2023
Summary:
Pull Request resolved: #1519

See facebook/docusaurus#9629

We use prism to render the code for the inline editor. Prism renders colors to style directly, and the color chosen is dependent on a setting that may not be available at SSR time.

This adds an SSR-specific representation of the code, missing some of the nuances in token colorization (similar to facebook/docusaurus#7373). This adds a little bit of jank compared to perfect SSR, but fixes cases where the mode is incorrect, and is a lot less jank then the more generic solution used by theme-live-codeblock of keeping the rendering of the opposite color until rehydration.

Preview: https://yoga-website-next-git-fork-nickgerleman-exp-2f8171-fbopensource.vercel.app/

Reviewed By: yungsters

Differential Revision: D52163722

fbshipit-source-id: 312dc52134f0084d40f78147190151700ee10ff7
@finom
Copy link

finom commented Dec 29, 2023

I've solved this problem by using useEffect and localStorage. In my case I needed to invert an image but you can call any other function.

const { colorMode } = useColorMode();
const rendersRef = useRef(1);

useEffect(() => {
  // eslint-disable-next-line no-undef
  const localStorageColorMode = localStorage.getItem('theme');
  if (rendersRef.current === 1 && localStorageColorMode) {
    // eslint-disable-next-line no-undef, @typescript-eslint/no-unsafe-member-access
    document.querySelector('.invert').style.filter = localStorageColorMode === 'dark' ? 'invert(1)' : 'none';
  }
  rendersRef.current += 1;
}, [colorMode]);

@slorber
Copy link
Collaborator

slorber commented Jan 2, 2024

@finom this looks like a bad solution leading to FOUC. You don't want to wait for React hydration to invert the image, you want it to be displayed inverted as soon as possible, even if JS is fully disabled.

Try our ThemedImage component instead: https://docusaurus.io/docs/markdown-features/assets#themed-images

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug An error in the Docusaurus core causing instability or issues with its execution closed: duplicate This issue or pull request already exists in another issue or pull request
Projects
None yet
Development

No branches or pull requests

3 participants