简体   繁体   中英

removing an imported CSS from react and replace it with new CSS

I have a huge react app which has 4 main huge CSSs(darkLTR, lightLTR, darkRTL, lightRTL) which is not ideal i know, but it was a website template that my boss bought for me and asked me to use it(i wanted to use Material ui which is awesome for theme handling but sadly i wasn't allowed). It came with two different templates. Dark mode and light mode. They told me that they want to have both dark and light mode and even RTL support. So i had to translate ltr into rtl and add toggle buttons to switch between different themes and languages. I did all that and now i have 4 CSS files for all the styles. They all have same class names and rules. The only different is directions and colors. I import these CSS files like below:

export default function importStylesheets() {
    const theme = localStorage.getItem('theme')
    const direction = localStorage.getItem('direction')
    if (theme === 'dark' && direction === 'rtl') {
        import('./css/styleDarkrtl.css')
    } else if (theme === 'dark' && direction === 'ltr') {
        import('./css/styleDarkltr.css')
    } else if (theme === 'light' && direction === 'rtl') {
        import('./css/styleLightrtl.css')
    } else if (theme === 'light' && direction === 'ltr') {
        import('./css/styleLightltr.css')
    }
}

Now my problem begins. Imagine we are in darkltr mode and darkltr CSS is loaded already. Now user switches the theme toggle button. So importStylesheets() function will be called again and this time the value of theme will be light. So this time lightltr will get imported and the whole theme will get updated and everything seems cool. But what if user switch the theme toggle button once again? Nothing happens. Because now that i call importStylesheets() function, it won't re-import darkltr CSS because it is already inside application cache. And the higher priority will be with the last imported file (in this case, lightltr). So nothing will change. I came up with a temporary solution to reload the whole application and load it with localStorage and then call importStylesheets() function. But that's a huge disadvantage because one of the reasons that we use react apps is to have a good and fast user experience. So my question is, can someone help me to clear the imported content? Or whatever. I just need to be abled to switch between different CSSs at anytime or make them top priority. Just help me to achieve this goal please. Much appreciated.

UPDATE : I found this part of code in my bundled file. Thought it might help

function importStylesheets() {
  const theme = localStorage.getItem('theme');
  const direction = localStorage.getItem('direction');

  if (theme === 'dark' && direction === 'rtl') {
    Promise.all(/*! import() */[__webpack_require__.e(0), __webpack_require__.e(1)]).then(__webpack_require__.t.bind(null, /*! ./css/styleDarkrtl.css */ "./src/css/styleDarkrtl.css", 7));
  } else if (theme === 'dark' && direction === 'ltr') {
    Promise.all(/*! import() */[__webpack_require__.e(0), __webpack_require__.e(3)]).then(__webpack_require__.t.bind(null, /*! ./css/styleDarkltr.css */ "./src/css/styleDarkltr.css", 7));
  } else if (theme === 'light' && direction === 'rtl') {
    Promise.all(/*! import() */[__webpack_require__.e(0), __webpack_require__.e(2)]).then(__webpack_require__.t.bind(null, /*! ./css/styleLightrtl.css */ "./src/css/styleLightrtl.css", 7));
  } else if (theme === 'light' && direction === 'ltr') {
    Promise.all(/*! import() */[__webpack_require__.e(0), __webpack_require__.e(4)]).then(__webpack_require__.t.bind(null, /*! ./css/styleLightltr.css */ "./src/css/styleLightltr.css", 7));
  }
}

Ok, the thing is that there are at least two different mechanisms that are contributing to the current behavior (Webpack module engine and Webpack's style-loader ).

When you use Create React App, your code is bundled using Webpack which is responsible of module creation, loading and execution.

Webpack emulates NodeJS module caching strategy, so you are right, each module is only executed once (you could delete the corresponding entry from require.cache using module ID to force a module to be executed multiple times, but in your case it would pollute your <head> with new <style> tags each time you decide to load a css file. Old ones would not be removed).

In fact, style-loader converts your css code into a pseudo JavaScript module. When executed, styles are applied depending on specified options. By default, <style> tags are created.

So, this is why your code behaves like you see. When calling import() , Webpack module engine (Webpack Runtime) searches for the corresponding module (that is the __webpack_require__ thing in your update) and executes it. Then, internally, a wrapper set by style-loader inserts your styles by using <style> tags. Finally, Webpack adds the module to the cache and the next time it will not be executed again, but the latest export will be returned instead (which in this case is an object containing all your classes names).

It happens that style-loader has a feature that I think perfectly matches your use-case and it is called lazyStyleTag (see here ). But you will have to change Webpack's config which is not that straightforward, specially when using Create React App.

If you need help to change Webpack's config, please let me know in the comments and I will try to guide you through the process.

However, at the end, you should be able to do something like this:

let previousStylesheet = null;

export async function importStylesheets() {
    const theme = localStorage.getItem('theme');
    const direction = localStorage.getItem('direction');

    let newStylesheet = null;

    if (theme === 'dark' && direction === 'rtl') {
        newStylesheet = await import('./css/styleDarkrtl.css');
    } else if (theme === 'dark' && direction === 'ltr') {
        newStylesheet = await import('./css/styleDarkltr.css');
    } else if (theme === 'light' && direction === 'rtl') {
        newStylesheet = await import('./css/styleLightrtl.css');
    } else if (theme === 'light' && direction === 'ltr') {
        newStylesheet = await import('./css/styleLightltr.css');
    }

    if (previousStylesheet) {
      previousStylesheet.unuse();
    }

    if (newStylesheet) {
      newStylesheet.use();
    }

    previousStylesheet = newStylesheet;
}

Your class names would be accessible like this:

import styles from './css/styleDarkrtl.css';

const elem = <div className={styles.locals['my-class']} />;

For everyone who is still wondering how to achieve this goal easily, you have two options:
1- You can use pure js to achieve this. First of all place all your css in the public folder of your app. Then you can do it with something like this:

const head = document.querySelector("head")
let link = document.getElementById("css-link")
if (!link) { // for first render and ensuring that the link does not already exist
    link = document.createElement('link')
    link.rel = "stylesheet"
    link.href = `/css/style${theme === 'dark' ? 'Dark' : 'Light'}${direction}.css`
    link.id = "css-link"
} else {
    link.href = `/css/style${theme === 'dark' ? 'Dark' : 'Light'}${direction}.css`
}

2- With the help of react-helmet package, you have no need to use pure js and having pain. All you need to do is to place all your css in public folder and then do something like below:

import { Helmet } from 'react-helmet'
.
.
.
<Helmet >
        <link rel="stylesheet" href={`/css/style${theme === 'dark' ? 'Dark' : 'Light'}${direction}.css`} />
 </Helmet>
.
.
.

Using the 2 ways i mentioned above, i could simply switch between different CSSs easily.
Important note
You have to start your addressing in href attributes with "/" , not with "./" (if your website is served at root)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM