React: antd change dark or light theme on runtime

Viewed 3750

There is a similar question posted in stack overflow, but the posted answer cannot help me. I have followed the steps, but there is no effect. Basically, I have no idea where to put this config-overrides.js as shown in the answer, and it seems many modules yet to be installed.

Besides that, I have also followed the antd website to configure the web application to run a dark theme. It is able to change to dark theme after web application has started. The following is the code, but how can I select the theme i.e. dark or light I want on runtime?

Project Structure:

Project structure

package.json: (changes on scripts portion)

{
  "name": "testout",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@ant-design/dark-theme": "^2.0.2",
    "@craco/craco": "^5.6.4",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "antd": "^4.6.4",
    "antd-theme": "^0.3.4",
    "craco-less": "^1.17.0",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "3.4.3"
  },
  "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
   

App.js

import React from 'react';
import './App.less';
import 'antd/dist/antd.less';
import { Button, Select } from 'antd';
function App() {
  const { Option } = Select;

  function handleChange(value) {
    console.log(`selected ${value}`);
  }
  return (
    <div className="App">
      <Button type="primary">Button</Button>
      <Select defaultValue="lucy" style={{ width: 120 }} onChange={handleChange}>
        <Option value="jack">Jack</Option>
        <Option value="lucy">Lucy</Option>
        <Option value="disabled" disabled>
          Disabled
      </Option>
        <Option value="Yiminghe">yiminghe</Option>
      </Select>

    </div>
  );
}

export default App;

craco.config.js

const CracoLessPlugin = require('craco-less');
const { getThemeVariables } = require('antd/dist/theme');


module.exports = {
  plugins: [
    {
      plugin: CracoLessPlugin,
      options: {
        lessLoaderOptions: {
          lessOptions: {
            modifyVars:  getThemeVariables({
                         dark: true, 
                         compact: true,
            }),
            javascriptEnabled: true,
          },
        },
      },
    },
  ],
};

Output:

Project structure

2 Answers

I have managed to get the answer by referring to this link. It does not only has a detailed information about the theme switcher to switch between light and dark, it also shows the method of changing the color of the antd component.

I have prepared a github repo which has a simplified version of just switching black and light themes for demonstration only.

An antd Select component is placed at the top of the webpage for you to select 'dark or 'light' theme. The following is the snapshot of switching theme at runtime.

dark theme     light theme

The following is the step:

  1. Install the packages.

    npm i react-app-rewire-antd-theme antd-theme antd
    
  2. Copy the ~public/color.less into your ~public folder. You may find this file in ~public/color.less in the github link above.

  3. Copy the following codes into your ~/public/index.html bottom of the <body> tag or after the body content e.g. <div id="root"></div> for default react index.html.

    <link rel="stylesheet/less" type="text/css" href="color.less" />
    <script>
          window.less = {
             async: true,
             env: 'production'
    };
    </script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script>
    
  4. Copy ~src/components/dark.json and ~src/components/light.json to the folder you want. You may find these two files in ~src/components/ in the github link above.

  5. Use the following code to switch theme by calling dark.json or light.json ~src/components/ThemeSelector. Anyway, the code is a sample, you could build your own code to switch theme.

      //sample code
      let vars = value === 'light' ? lightVars : darkVars;
      vars = { ...vars, '@white': '#fff', '@black': '#000' };
      window.less.modifyVars(vars).catch(error => {});
      setTheme(value)
    

Simple and effective (Uses React.Lazy )

Code reference:

Stackblitz Code reference and demo: https://stackblitz.com/edit/react-6ub59h?file=index.js

  • This answer does not dynamically changes theme,
  • Instead, it uses localStorage to keep the preference of the user. ( can use other methods instead of localStorage as well )
  • Whenever the user changes the theme it reloads the application

Dark Mode Screenshot
Light Mode Screenshot

index.js

import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import { Button, Space } from 'antd';
import "antd/dist/antd.css";
import "./index.css";
import App from "./app";

export const themeConst = {
  DARK: "Dark ",
  LIGHT: "Light ☀️"
};

const LightThemeComponent = React.lazy(() => import("./light"));
const DarkThemeComponent = React.lazy(() => import("./dark"));

const curTheme =
  localStorage.theme == themeConst.DARK ? themeConst.DARK : themeConst.LIGHT;

const toggleTheme = function (isDarkMode) {
  if (curTheme == themeConst.DARK) {
    localStorage.setItem("theme", themeConst.LIGHT);
  } else {
    localStorage.setItem("theme", themeConst.DARK);
  }
  window.location.reload();
};

ReactDOM.render(
  <Space direction="vertical">
    <Button onClick={toggleTheme}>Switch to {(curTheme == themeConst.LIGHT) ? themeConst.DARK : themeConst.LIGHT} Mode</Button>
    {curTheme == themeConst.DARK && (
      <Suspense fallback={<div>Loading...</div>}>
        <DarkThemeComponent>
          <App />
        </DarkThemeComponent>
      </Suspense>
    )}
    {curTheme == themeConst.LIGHT && (
      <Suspense fallback={<div>Loading...</div>}>
        <LightThemeComponent>
          <App />
        </LightThemeComponent>
      </Suspense>
    )}
  </Space>,
  document.getElementById("container")
);

dark.js

import React, { Suspense } from "react";
import "antd/dist/antd.dark.css";

const DarkApp = (props) => {
  return <>{props.children}</>;
};

export default DarkApp;
Related