Bootstrap Theme React Context Provider. Light and Dark mode. Import and unimport CSS files dynamically with webpack.
Tags: react, bootstrap, webpack
Published: 2022-08-05 06:57:00

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](https://webpack.js.org/loaders/style-loader/#lazystyletag)**

Go to your webpack config file and add the following rules
```JavaScript
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",
        ],
      },
    ],
  },
};
```

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

You probably have this
```JavaScript
light.css
dark.css 
// or
light.min.css
dark.min.css
```
Now will be this:
```JavaScript
light.lazy.css
dark.lazy.css
```

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...
```JavaScript
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);
```

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:
```JavaScript
import ThemeProvider from './ThemeProvider'

const App = () => {
   return (
     <ThemeProvider>
        <App />
     <ThemeProvider/>
   )
}
```

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

```JavaScript
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 

}

```


  [1]: https://webpack.js.org/loaders/style-loader/#lazystyletag