Skip to content

Highlight component renders the wrong theme even though props are passed correctly #112

Closed
@cjones26

Description

@cjones26

Hello,

I am using Gatsby in combination with prism-react-renderer and I've come across an issue which is stumping me. I am attempting to avoid FOUC in prism-react-renderer when implementing a dark / light mode by injecting the dark class into my HTML before rendering the page via gatsby-ssr.js, like so:

gatsby-ssr.js

exports.onRenderBody = ({ setHeadComponents, setBodyAttributes }) => {
  setBodyAttributes({
    className: 'bg-white dark:bg-midnight-express',
  });

  setHeadComponents([
    <script
      key="theme"
      dangerouslySetInnerHTML={{
        __html: `(function() {  
            function setTheme(theme) {
              window.theme = theme;

              if (theme === 'dark') {
                document.documentElement.classList.toggle('dark');
              }
            };

            let preferredTheme;

            try {
              preferredTheme = localStorage.getItem('theme');
            } catch (e) {}

            let darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
            
            setTheme(preferredTheme || (darkQuery.matches ? 'dark' : 'light'));
          })();`,
      }}
    />,
  ]);
};

While this all works fine for the page theme itself, the issues crop up with my CodeBlock rendering--as you can see in the code below, when I set the default theme I first check whether we have a window -- if we do, I check whether the document.documentElement.classList contains our dark theme--if it does, we set this as the default theme. If our window is undefined, such as during the SSR, we simply return the light theme. This code works perfectly in development mode, and even appears to work perfectly when being served as a static bundle, unfortunately the Highlight component renders the wrong theme even when receiving the correct props. Please see below:

CodeBlock.tsx

/* eslint-disable react/no-array-index-key */
import React, { useState } from 'react';
import Highlight, { defaultProps, Language } from 'prism-react-renderer';
import lightTheme from 'prism-react-renderer/themes/vsLight';
import darkTheme from 'prism-react-renderer/themes/palenight';
import Themes from 'constants/Themes';

interface CodeBlockProps {
  children: {
    props: {
      children: string;
      className: string;
    };
  };
  className: string;
}

export const getInitialTheme = (): Themes => {
  if (typeof window !== `undefined`) {
    return window.document.documentElement.classList.contains(Themes.DARK) ? Themes.DARK : Themes.LIGHT;
  }

  return Themes.LIGHT;
};

export default ({ children }: CodeBlockProps) => {
  // Pull the className
  const language: Language | string = children.props.className?.replace(/language-/, '') || '';
  const themes = {
    [Themes.DARK]: darkTheme,
    [Themes.LIGHT]: lightTheme,
  };
  const [theme] = useState<Themes>(getInitialTheme());

  console.log('theme: ', theme);
  console.log('themes[theme]: ', themes[theme]);

  return (
    <Highlight
      Prism={defaultProps.Prism}
      theme={themes[theme]}
      code={children.props.children.trim()}
      language={language as Language}
    >
      {({ className, style, tokens, getLineProps, getTokenProps }) => (
        <pre className={className} style={{ ...style }}>
          {tokens.map((line, index) => {
            const lineProps = getLineProps({ line, key: index });
            return (
              <div key={`item-${index}`} className={lineProps.className} style={lineProps.style}>
                {line.map((token, key) => {
                  const tokenProps = getTokenProps({ token, key });
                  return (
                    <span key={`line-${key}`} style={tokenProps.style} className={tokenProps.className}>
                      {tokenProps.children}
                    </span>
                  );
                })}
              </div>
            );
          })}
        </pre>
      )}
    </Highlight>
  );
};

As you can see below, the dark theme is correctly identified and rendered when running in development mode:

Develop mode

And even after building a production bundle and serving the dark theme is identified correctly, the correct props are passed, but the rendered HTML is incorrect--as you can see, the hex values on the theme are correct:

Prod build

The props passed into the Highlight component are correct:

Prod Build 2

Yet the HTML is incorrect and defaulting to the default light theme (and vice versa if I switch the default to dark):

Prod Build 2

You can access my repository with this code here:

https://github.com/cjones26/niggling-aspirations-blog/tree/738a7736e579e51dc7eb349b1404781e0007deef

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions