Search code examples
javascriptcssreactjsstyled-componentsemotion

Enable Global Theming with Emotion?


I have followed https://github.com/emotion-js/emotion/issues/546 in which Emotion's author Kye mentions a solution although I don't understand it completely.

So I made a small CodeSandBox that implements the details provided in the issue. How can I make background-color theme work in injectGlobal?


Solution

  • I found the solution. The complete solution can be found at https://codesandbox.io/s/r76p996zym or https://github.com/deadcoder0904/emotion-global-theming

    Make a theme.js file containing your application themes

    theme.js

    export const theme = {
      LIGHT: {
        textColor: "black",
        bgColor: "white"
      },
      DARK: {
        textColor: "white",
        bgColor: "black"
      }
    };
    

    Wrap Global Component in withTheme & it should take a theme prop

    Global.js

    import React from "react";
    import { injectGlobal } from "react-emotion";
    import { withTheme } from "emotion-theming";
    
    class Global extends React.Component {
      componentDidUpdate(prevProps) {
        if (this.props.theme.bgColor !== prevProps.theme.bgColor) {
          window.document.body.style.backgroundColor = this.props.theme.bgColor;
        }
        if (this.props.theme.textColor !== prevProps.theme.textColor) {
          window.document.body.style.color = this.props.theme.textColor;
        }
      }
    
      render() {
        injectGlobal`
          color: ${this.props.theme.textColor};
          background-color: ${this.props.theme.bgColor};
        `;
        return React.Children.only(this.props.children);
      }
    }
    
    export default withTheme(Global);
    

    And then wrap your App component with Global component. As Global component requires theme it should be wrapped in ThemeProvider

    index.js

    import React from "react";
    import ReactDOM from "react-dom";
    import { ThemeProvider } from "emotion-theming";
    
    import Global from "./injectGlobal";
    import { theme } from "./theme";
    
    class App extends React.Component {
      state = {
        isLight: true,
        title: "Light Theme",
        theme: theme.LIGHT
      };
    
      _toggleTheme = () => {
        const { isLight } = this.state;
        const title = isLight ? "Dark Theme" : "Light Theme";
        const newTheme = isLight ? theme.DARK : theme.LIGHT;
        this.setState({
          isLight: !isLight,
          title,
          theme: newTheme
        });
      };
    
      render() {
        const { title, theme } = this.state;
        return (
          <ThemeProvider theme={theme}>
            <Global>
              <React.Fragment>
                <h1>{title}</h1>
                <button onClick={this._toggleTheme}>Toggle Theme</button>
              </React.Fragment>
            </Global>
          </ThemeProvider>
        );
      }
    }
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);
    

    Note - This answer is only valid until Emotion 10 releases & API changes. If Emotion version is less than 10, then use this solution.