Skip to content

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

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

Closed
cjones26 opened this issue Apr 13, 2021 · 2 comments

Comments

@cjones26
Copy link

cjones26 commented Apr 13, 2021

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!

@cjones26
Copy link
Author

After messing with this for entirely too long I have found a solution--instead of attempting to utilize the "theme" prop, I instead simply created 2 CSS files--one for dark mode & one for light mode -- this allowed the code in gatsby-ssr.js to handle everything due to the cascading nature of CSS -- when the dark class is applied to the body, the dark CSS is utilized -- when it is not, the light CSS is utilized.

prism-palenight-theme.css

.dark code[class*='language-'],
.dark pre[class*='language-'] {
  color: #a6accd;
  font-family: 'Consolas', 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace;
  direction: ltr;
  text-align: left;
  white-space: pre;
  word-spacing: normal;
  word-break: normal;
  font-size: 0.9em;
  line-height: 1.2em;

  -moz-tab-size: 4;
  -o-tab-size: 4;
  tab-size: 4;

  -webkit-hyphens: none;
  -moz-hyphens: none;
  -ms-hyphens: none;
  hyphens: none;
}

.dark pre > code[class*='language-'] {
  font-size: 1em;
}

.dark pre[class*='language-']::-moz-selection,
.dark pre[class*='language-'] ::-moz-selection,
.dark code[class*='language-']::-moz-selection,
.dark code[class*='language-'] ::-moz-selection {
  background: #292d3e;
}

.dark pre[class*='language-']::selection,
.dark pre[class*='language-'] ::selection,
.dark code[class*='language-']::selection,
.dark code[class*='language-'] ::selection {
  background: #292d3e;
}

.dark :not(pre) > code[class*='language-'],
.dark pre[class*='language-'] {
  background: #292d3e;
}

.dark :not(pre) > code[class*='language-'] {
  padding: 0.2em;
  padding-top: 1px;
  padding-bottom: 1px;
}

.dark pre[class*='language-'] {
  padding: 1em;
  margin: 0.5em 0;
  overflow: auto;
  border: 1px solid #dddddd;
  background-color: #292d3e;
}

.dark .token.script.language-javascript {
  color: #a6accd;
}

.dark .token.comment,
.dark .token.block-comment,
.dark .token.prolog,
.dark .token.doctype,
.dark .token.cdata {
  color: #676e95;
}

.dark .token.attr-name,
.dark .token.namespace,
.dark .token.deleted {
  color: #bb80b3;
}

.dark .token.function,
.dark .token.function-name {
  color: #82aaff;
}

.dark .token.unit,
.dark .token.url,
.dark .token.boolean,
.dark .token.number {
  color: #f78c6c;
}

.dark .token.color,
.dark .token.hexcode,
.dark .token.builtin,
.dark .token.property,
.dark .token.class,
.dark .token.class-name,
.dark .token.constant,
.dark .token.symbol {
  color: #ffcb6b;
}

.dark .token.id,
.dark .token.selector,
.dark .token.important,
.dark .token.atrule,
.dark .token.keyword {
  color: #c792ea;
}

.dark .token.pseudo-class,
.dark .token.pseudo-element,
.dark .token.inserted,
.dark .token.attribute,
.dark .token.string,
.dark .token.char,
.dark .token.attr-value,
.dark .token.regex,
.dark .token.variable {
  color: #c3e88d;
}

.dark .token.punctuation,
.dark .token.operator,
.dark .token.entity,
.dark .token.url {
  color: #89ddff;
}

.dark .token.tag {
  color: #f07178;
}

.dark .token.parameter,
.dark .token.deleted {
  color: #ff5370;
}

.dark .token.important,
.dark .token.bold {
  font-weight: bold;
}

.dark .token.italic {
  font-style: italic;
}

.dark .token.entity {
  cursor: help;
}

prism-vslight-theme.css

/**
 * VS theme by Andrew Lock (https://andrewlock.net)
 * Inspired by Visual Studio syntax coloring
 */

code[class*='language-'],
pre[class*='language-'] {
  color: #393a34;
  font-family: 'Consolas', 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace;
  direction: ltr;
  text-align: left;
  white-space: pre;
  word-spacing: normal;
  word-break: normal;
  font-size: 0.9em;
  line-height: 1.2em;

  -moz-tab-size: 4;
  -o-tab-size: 4;
  tab-size: 4;

  -webkit-hyphens: none;
  -moz-hyphens: none;
  -ms-hyphens: none;
  hyphens: none;
}

pre > code[class*='language-'] {
  font-size: 1em;
}

pre[class*='language-']::-moz-selection,
pre[class*='language-'] ::-moz-selection,
code[class*='language-']::-moz-selection,
code[class*='language-'] ::-moz-selection {
  background: #c1def1;
}

pre[class*='language-']::selection,
pre[class*='language-'] ::selection,
code[class*='language-']::selection,
code[class*='language-'] ::selection {
  background: #c1def1;
}

/* Code blocks */
pre[class*='language-'] {
  padding: 1em;
  margin: 0.5em 0;
  overflow: auto;
  border: 1px solid #dddddd;
  background-color: white;
}

/* Inline code */
:not(pre) > code[class*='language-'] {
  padding: 0.2em;
  padding-top: 1px;
  padding-bottom: 1px;
  background: #f8f8f8;
  border: 1px solid #dddddd;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
  color: #008000;
  font-style: italic;
}

.token.namespace {
  opacity: 0.7;
}

.token.string {
  color: #a31515;
}

.token.punctuation,
.token.operator {
  color: #393a34; /* no highlight */
}

.token.url,
.token.symbol,
.token.number,
.token.boolean,
.token.variable,
.token.constant,
.token.inserted {
  color: #36acaa;
}

.token.atrule,
.token.keyword,
.token.attr-value,
.language-autohotkey .token.selector,
.language-json .token.boolean,
.language-json .token.number,
code[class*='language-css'] {
  color: #0000ff;
}

.token.function {
  color: #393a34;
}

.token.deleted,
.language-autohotkey .token.tag {
  color: #9a050f;
}

.token.selector,
.language-autohotkey .token.keyword {
  color: #00009f;
}

.token.important,
.token.bold {
  font-weight: bold;
}

.token.italic {
  font-style: italic;
}

.token.class-name,
.language-json .token.property {
  color: #2b91af;
}

.token.tag,
.token.selector {
  color: #800000;
}

.token.attr-name,
.token.property,
.token.regex,
.token.entity {
  color: #ff0000;
}

.token.directive.tag .tag {
  background: #ffff00;
  color: #393a34;
}

/* overrides color-values for the Line Numbers plugin
  * http://prismjs.com/plugins/line-numbers/
  */
.line-numbers .line-numbers-rows {
  border-right-color: #a5a5a5;
}

.line-numbers-rows > span:before {
  color: #2b91af;
}

/* overrides color-values for the Line Highlight plugin
 * http://prismjs.com/plugins/line-highlight/
 */
.line-highlight {
  background: rgba(193, 222, 241, 0.2);
  background: -webkit-linear-gradient(left, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0));
  background: linear-gradient(to right, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0));
}

@emcfarlane
Copy link

I have the same issue. Doesn't toggle correctly between themes.

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

No branches or pull requests

2 participants