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",
],
},
],
},
};
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
Now will be this:
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...
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:
import ThemeProvider from './ThemeProvider'
const App = () => {
return (
<ThemeProvider>
<App />
<ThemeProvider/>
)
}
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
}
Top comments (1)
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 intocss
before passing them down tostyleLoader
. Is this kind of setup possible? Do you see any issues with that approach?