DEV Community

Cover image for Bootstrap Theme React Context Provider. Light and Dark mode. Import and unimport CSS files dynamically with webpack.
Rolando Niubo
Rolando Niubo

Posted on • Edited on

Bootstrap Theme React Context Provider. Light and Dark mode. Import and unimport CSS files dynamically with webpack.

If you are here you most likely are trying to make a Theme Provider around a React application, probably to apply Light/Dark modes to your App, but Bootstrap is the CSS solution for the product in hand.

How to achieve this, you are going to need 2 css files, each file will provide each theme color, one will provide a dark mode, and the other will provide the light mode. Usually you will use the bootstrap css file and modify it, or purchase some themes on the internet, which will give you 2 css files, but actually this process can be done even by creating just 2 css files and putting your own styles inside. Then we are going to create a ThemeProvider doing a simple React context provider, and we will condition and toggle the imports of those to CSS files.

So to do this, first thing that comes to mind, is use React lazy and suspense, this way we can lazy import both files when we need it. The problem with this approach is that it will work only one time. First it would import the first CSS file, then when toggle the first time it will import the second file, but it wouldn't get rid of the first import, as React re-renders dont do that.

What we actually need is Toggle the imports, first import one of them, and then when we import the second one, we need to unimport the first one. To do this, we need to use a Webpack feature called lazyStyleTag. This feature allow us to import styles and lazy boundle them. So basically we can boundle them and unboundle them any time we want.

First let's add webpack lazyStyleTag

Go to your webpack config file and add the following rules

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        // Probly you already have this rule, add this line
        exclude: /\.lazy\.css$/i,
        use: ["style-loader", "css-loader"],
      },
      // And add this rule
      {
        test: /\.lazy\.css$/i,
        use: [
          { loader: "style-loader", options: { injectType: "lazyStyleTag" } },
          "css-loader",
        ],
      },
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

Now take your CSS files and change their name to the lazy named convention, as their documentation says you should.

You probably have this

light.css
dark.css 
// or
light.min.css
dark.min.css
Enter fullscreen mode Exit fullscreen mode

Now will be this:

light.lazy.css
dark.lazy.css
Enter fullscreen mode Exit fullscreen mode

Then create your React Theme Provider in a simple React context, this context will wrap your Application, so it will conditionaly boundle and unboundle each CSS file everytime the context state changes. This context state is going to be availabe anywhere inside your app as well as the setter via a custom hook we will export from the same file, check this out:

I'm using typescript, but you don't have to...

import React, {
    useEffect, createContext, useState, useContext,
} from 'react';
import { Nullable } from 'types';

// Now import both of your CSS files here like this:

// Import of CSS file number 1
import LightMode from './light.lazy.css';
// Import of CSS file number 2
import DarkMode from './dark.lazy.css';

// Typescript context interface, you don't need this if not // using TS
interface IContext {
    theme: Nullable<string>
    toggleTheme: () => void
}

const Context = createContext<IContext>({
    theme: null,
    toggleTheme: () => { },
});

// Your Provider component that returns 
// the Context.Provider
// Let's also play with the sessionStorage, 
// so this state doesn't
// brake with browser refresh or logouts
const ThemeProvider: React.FC = ({ children }) => {
    // Im initialazing here the state with any existing value in the 
    //sessionStorage, or not...
    const [theme, setTheme] = useState<Nullable<string>>(sessionStorage.getItem('themeMode') || 'dark');

    // this setter Fn we can pass down to anywhere
    const toggleTheme = () => {
        const newThemeValue = theme === 'dark' ? 'light' : 'dark';
        setTheme(newThemeValue);
        sessionStorage.setItem('themeMode', newThemeValue);
    };

    // Now the magic, this lazy css files you can use or unuse
    // This is exactly what you need, import the CSS but also unimport
    // the one you had imported before. An actual toggle of import in a 
    // dynamic way.. brought to you by webpack
    useEffect(() => {
        if (theme === 'light') {
            DarkMode.unuse();
            LightMode.use();
        } else if (theme == 'dark') {
            LightMode.unuse();
            DarkMode.use();
        }
    }, [theme]);


    return (
        <Context.Provider value={{ theme, toggleTheme }}>
            {children}
        </Context.Provider>
    );
};

export default ThemeProvider;
// This useTheme hook will give you the context anywhere to set the state of // theme and this will toggle the styles imported
export const useTheme = () => useContext(Context);
Enter fullscreen mode Exit fullscreen mode

Remember to put this state on the sessionStorage like in this example so your user has the state available every time it comes back or refreshes the page

Wrap your App in the provider:

import ThemeProvider from './ThemeProvider'

const App = () => {
   return (
     <ThemeProvider>
        <App />
     <ThemeProvider/>
   )
}
Enter fullscreen mode Exit fullscreen mode

Now just toggle the CSS imports of your application using your cool useTheme hook

import { useTheme } from './yourContextFile';


// inside your component
const AnyComponentDownTheTree = () => {

   const { theme, toggleTheme } = useTheme()

   // use the toggleTheme function to toggle 
   // and the theme actual value 
   // for your components, you might need 
   // disable something or set active a 
   // switch, etc, etc 

}

Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
brianhvb profile image
Brian Houle

Great article @niubo

Quick question. My assumption is that this should also work for .scss/.sass files as long as Webpack is configured to compile them into css before passing them down to styleLoader. Is this kind of setup possible? Do you see any issues with that approach?