Module scss ampersand class selector not working - reactjs

I have a simple scss file with
.navItem {
cursor: pointer;
&.active {
color: $primaryPurple;
}
&:hover {
color: $primaryPurple;
}
span {
margin-left: 10px;
}
}
For some reason, the :hover works but .active doesn't work. As you can see in the image below, li clearly has the active class but I don't see the font color css changed.

TL;DR: Pass the name as a JavaScript Object, not like a simple string.
Actually, this issue comes from the css-modules configuration. Obviously, based on the AdminSideBar_navItem__1aFBc in the embed picture. your css-modules configuration in the Webpack config is:
// webpack config
module.exports = {
~~~
module: {
~~~
rules: [
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]_[local]_[hash:base64:5]', // <=== Attention to this line
sourceMap: true,
}
},
~~~
],
}),
],
},
~~~
};
So, when you are using css-modules it means you will have so professional advantages like a more compressed css bundle file or full hashed unreadably uglified bundle. for these benefits, you should use CSS class names like a JavaScript object, pay attention to the following ReactJS code alongside using css-modules:
import React from 'react';
import cn from 'classnames'; // <=== install it to have easy coding
import styles from '[pathToScssFiles]/styles.scss';
const MyComponent = ({ isActive }) => (
<li className={cn(styles.navItem, isActive && styles.active)} />
);
I hope, you understand my point, you passed the styles.active as a simple string: "active". so css-modules cannot get your class name as an object and pass it exactly like what it got, as a simple string, hence, "active" **BUT** in the CSS bundle file the css-modulesuse the'[name][local][hash:base64:5]'` pattern to deform its name.
Your :hover should work and it works because anyone cannot change it, it's a pseudo-class.
For sure, write a weird property for .active:
.navItem {
cursor: pointer;
&.active {
fill: yellow;
}
Now seek for the fill: yellow in the CSS bundle that Webpack makes for you. you see it is:
.AdminSideBar_navItem__1aFBc .AdminSideBar_active__9be2a {
fill: yellow;
}
Note: the ~~~ means etc.

Providing you jsx code would help, but I will take a guess that you use active as a string literal, so your code looks like
import styles from './componentName.module.css';
// and then in the component
<li className={`nav-item ${styles.AdminSideBar_navItem} ${whateverCondition ? 'active' : ''}`}>
...children...
</li>
The problem here is that active class also has a suffix just like AdminSideBar_navItem. And in order to make it work you need to access it the same way you access AdminSideBar_navItem. So the code should be
<li className={`nav-item ${styles.AdminSideBar_navItem} ${whateverCondition ? styles.active : ''}`}>
...children...
</li>
and in html you will see
<li class="nav-item AdminSideBar_navItem_abcd active_bcde">
I dropped a simple example in this codesandbox, you can play around with it and see how it works.

Related

Dynamically build classnames in TailwindCss

I am currently building a component library for my next project with TailwindCss, I just ran into a small issue when working on the Button component.
I'm passing in a prop like 'primary' or 'secondary' that matches a color I've specified in the tailwind.config.js then I want to assign that to the button component using Template literals like so: bg-${color}-500
<button
className={`
w-40 rounded-lg p-3 m-2 font-bold transition-all duration-100 border-2 active:scale-[0.98]
bg-${color}-500 `}
onClick={onClick}
type="button"
tabIndex={0}
>
{children}
</button>
The class name comes through in the browser just fine, it shows bg-primary-500 in the DOM, but not in the applied styles tab.
The theming is configured like so:
theme: {
extend: {
colors: {
primary: {
500: '#B76B3F',
},
secondary: {
500: '#344055',
},
},
},
},
But it doesn't apply any styling. if I just add bg-primary-500 manually it works fine.
I'm honestly just wondering if this is because of the JIT compiler not picking dynamic classnames up or if I'm doing something wrong (or this is just NOT the way to work with tailWind).
Any help is welcome, thanks in advance!
So after finding out that this way of working is not recommended and that JIT doesn't support it (Thanks to the generous commenters). I have changed the approach to a more 'config' based approach.
Basically I define a const with the basic configuration for the different props and apply those to the component. It's a bit more maintenance work but it does the job.
Here is the example of a config. (Currently without typing) and up for some better refactoring but you'll get the idea.
const buttonConfig = {
// Colors
primary: {
bgColor: 'bg-primary-500',
color: 'text-white',
outline:
'border-primary-500 text-primary-500 bg-opacity-0 hover:bg-opacity-10',
},
secondary: {
bgColor: 'bg-secondary-500',
color: 'text-white',
outline:
'border-secondary-500 text-secondary-500 bg-opacity-0 hover:bg-opacity-10',
},
// Sizes
small: 'px-3 py-2',
medium: 'px-4 py-2',
large: 'px-5 py-2',
};
Then I just apply the styling like so:
<motion.button
whileTap={{ scale: 0.98 }}
className={`
rounded-lg font-bold transition-all duration-100 border-2 focus:outline-none
${buttonConfig[size]}
${outlined && buttonConfig[color].outline}
${buttonConfig[color].bgColor} ${buttonConfig[color].color}`}
onClick={onClick}
type="button"
tabIndex={0}
>
{children}
</motion.button>
this way of writing Tailwind CSS classes is not recommended. Even JIT mode doesn't support it, to quote Tailwind CSS docs: "Tailwind doesn’t include any sort of client-side runtime, so class names need to be statically extractable at build-time, and can’t depend on any sort of arbitrary dynamic values that change on the client"
EDIT: Better implementation 2022 - https://stackoverflow.com/a/73057959/11614995
Tailwind CSS does not support dynamic class names (see here). However, there's still a way to accomplish this. I needed to use dynamically build class names in my Vue3 application. See the code example below.
Upon build tailwind scanes your application for classes that are in use and automatically purges all other classes (see here). There is however a savelist feature that you can use to exclude classes from purging - aka they will always make it to production.
I have created a sample code below, that I use in my production. It combines each color and each color shade (colorValues array).
This array of class names is passed into the safelist. Please note, that by implementing this feature you ship more css data to production as well as ship css classes you may never use.
const colors = require('./node_modules/tailwindcss/colors');
const colorSaveList = [];
const extendedColors = {};
const colorValues = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
for (const key in colors) {
// To avoid tailWind "Color deprecated" warning
if (!['lightBlue', 'warmGray', 'trueGray', 'coolGray', 'blueGray'].includes(key))
{
extendedColors[key] = colors[key];
for(const colorValue in colorValues) {
colorSaveList.push(`text-${key}-${colorValue}`);
colorSaveList.push(`bg-${key}-${colorValue}`);
}
}
}
module.exports = {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}"
],
safelist: colorSaveList,
theme: {
extend: {
colors: extendedColors
}
},
plugins: [
require('tailwind-scrollbar'),
]
}
For tailwind JIT mode or v3 that uses JIT, you have to ensure that the file where you export the object styles is included in the content option in tailwind.config.js, e.g.
content: ["./src/styles/**/*.{html,js}"],
If someone comes across in 2022 - I took A. Mrózek's answer and made a couple of tweaks to avoid deprecated warnings and an issue with iterating non-object pallettes.
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []
// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]
for (const colorName in tailwindColors) {
if (deprecated.includes(colorName)) {
continue
}
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]
const pallette = tailwindColors[colorName]
if (typeof pallette === "object") {
shades.forEach((shade) => {
if (shade in pallette) {
colorSafeList.push(`text-${colorName}-${shade}`)
colorSafeList.push(`bg-${colorName}-${shade}`)
}
})
}
}
// tailwind.config.js
module.exports = {
safelist: colorSafeList,
content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: tailwindColors,
},
},
plugins: [],
}
this might be a bit late, but for the people bumping this thread.
the simplest explaination for this is;
Dynamic Class Name does not work unless you configured Safelisting for the Dynamic class name,
BUT, Dynamic Class works fine so long as its a full tailwind class name.
its stated here
this will not work
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
but this one works
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
its states;
As long as you always use complete class names in your code, Tailwind
will generate all of your CSS perfectly every time.
the longer explanation;
Tailwind will scan all the files specified in module.exports.content inside tailwind.config.js and look for tailwind classes, it does not even have to be in a class attribute and can even be added in commented lines, so long as the full class name is present in that file and class name is not dynamically constructed; Tailwind will pull the styling for that class,
so in your case, all you have to do is put in the full class name inside that file for all the possible values of your dynamic class
something like this
<button className={ color === 'primary' ? 'bg-primary-500' : 'bg-secondary-500'}>
{children}
</button>
or the method I would prefer
<!-- bg-primary-500 bg-secondary-500 -->
<button className={`bg-${color}-500 `}>
{children}
</button>
here's another example, although its Vue, the idea would be the same for any JS framework
<template>
<div :class="`bg-${color}-100 border-${color}-500 text-${color}-700 border-l-4 p-4`" role="alert">
test
</div>
</template>
<script>
/* all supported classes for color props
bg-red-100 border-red-500 text-red-700
bg-orange-100 border-orange-500 text-orange-700
bg-green-100 border-green-500 text-green-700
bg-blue-100 border-blue-500 text-blue-700
*/
export default {
name: 'Alert',
props: {
color: {type: String, default: 'red'}
}
}
</script>
and the result would be this
<Alert color="red"></Alert> <!-- this will have color related styling-->
<Alert color="orange"></Alert> <!-- this will have color related styling-->
<Alert color="green"></Alert> <!-- this will have color related styling-->
<Alert color="blue"></Alert> <!-- this will have color related styling-->
<Alert color="purple"></Alert> <!-- this will NOT have color related styling as the generated classes are not pre-specified inside the file -->
Now could use safeListing
and tailwind-safelist-generator package to "pregenerate" our dynamics styles.
With tailwind-safelist-generator, you can generate a safelist.txt file for your theme based on a set of patterns.
Tailwind's JIT mode scans your codebase for class names, and generates CSS based on what it finds. If a class name is not listed explicitly, like text-${error ? 'red' : 'green'}-500, Tailwind won't discover it. To ensure these utilities are generated, you can maintain a file that lists them explicitly, like a safelist.txt file in the root of your project.
In v3 as Blessing said you can change the content array to support that.
I had this
const PokemonTypeMap = {
ghost: {
classes: "bg-purple-900 text-white",
text: "fantasma",
},
normal: {
classes: "bg-gray-500 text-white",
text: "normal",
},
dark: {
classes: "bg-black text-white",
text: "siniestro",
},
psychic: {
classes: "bg-[#fc46aa] text-white",
text: "psíquico",
},
};
function PokemonType(props) {
const pokemonType = PokemonTypeMap[props.type];
return (
<span
className={pokemonType.classes + " p-1 px-3 rounded-3xl leading-6 lowercase text-sm font-['Open_Sans'] italic"}
>
{pokemonType.text}
</span>
);
}
export default PokemonType;
something similar to your approach, then I moved the array to a JSON file, it thought was working fine, but was browser caché... so following Blessing's response, you can add .json like this
content: ["./src/**/*.{js,jsx,ts,tsx,json}"],
Finally I have this code, it's better in my view.
import PokemonTypeMap from "./pokemonTypeMap.json";
function PokemonType(props) {
const pokemonType = PokemonTypeMap[props.type];
return (
<span className={pokemonType.classes + " p-1 px-3 rounded-3xl leading-6 lowercase text-sm font-['Open_Sans']"}>
{pokemonType.text}
</span>
);
}
export default PokemonType;
Is it recommended to use dynamic class in tailwind ?
No
Using dynamic classes in tailwind-css is usually not recommended because tailwind uses tree-shaking i.e any class that wasn't declared in your source files, won't be generated in the output file.
Hence it is always recommended to use full class names
According to Tailwind-css docs
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS
Isn't there work around ?
Yes
As a last resort, Tailwind offers Safelisting classes.
Safelisting is a last-resort, and should only be used in situations where it’s impossible to scan certain content for class names. These situations are rare, and you should almost never need this feature.
In your example,you want to have 100 500 700 shades of colors. You can use regular expressions to include all the colors you want using pattern and specify the shades accordingly .
Note: You can force Tailwind to create variants as well:
In tailwind.config.js
module.exports = {
content: [
'./pages/**/*.{html,js}',
'./components/**/*.{html,js}',
],
safelist: [
{
pattern: /bg-(red|green|blue|orange)-(100|500|700)/, // You can display all the colors that you need
variants: ['lg', 'hover', 'focus', 'lg:hover'], // Optional
},
],
// ...
}
EXTRA: How to automate to have all tailwind colors in the safelist
const tailwindColors = require("./node_modules/tailwindcss/colors")
const colorSafeList = []
// Skip these to avoid a load of deprecated warnings when tailwind starts up
const deprecated = ["lightBlue", "warmGray", "trueGray", "coolGray", "blueGray"]
for (const colorName in tailwindColors) {
if (deprecated.includes(colorName)) {
continue
}
// Define all of your desired shades
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900]
const pallette = tailwindColors[colorName]
if (typeof pallette === "object") {
shades.forEach((shade) => {
if (shade in pallette) {
// colorSafeList.push(`text-${colorName}-${shade}`) <-- You can add different colored text as well
colorSafeList.push(`bg-${colorName}-${shade}`)
}
})
}
}
// tailwind.config.js
module.exports = {
safelist: colorSafeList, // <-- add the safelist here
content: ["{pages,app}/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {
colors: tailwindColors,
},
},
plugins: [],
}
Note: I have tried to summarize the answer in all possible ways, In the combination of all possible answers.Hope it helps
I had a similar issue, instead of passing all the possible configs, I just pass the data to the style property of the HTML. This is far more efficient!
or
Pass a class name as a prop and let the user of the package write the styles to that class.
const CustomComp = ({
keyColGap = 0,
keyRowGap = 0,
className = '',
}: Props) => {
const classNameToRender = (): string => {
return `m-1 flex flex-col ${className}`.trim();
};
const rowStylesToRender = (): React.CSSProperties | undefined => {
const styles: React.CSSProperties | undefined = { gap: `${keyRowGap}rem` };
return styles;
};
const colStylesToRender = (): React.CSSProperties | undefined => {
const styles: React.CSSProperties | undefined = { gap: `${keyColGap}rem` };
return styles;
};
return (
<div className={classNameToRender()} style={rowStylesToRender()}>
{layout.map((row) => {
return (
<div
className={`flex justify-around`}
style={colStylesToRender()}
key={row}
>
/* Some Code */
</div>
);
})}
</div>
}

I'm having this weird webpack.cache.PackageCacheStrategy

Below is the error on my console
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info - Using webpack 5. Reason: no custom webpack configuration in next.config.js https://nextjs.org/docs/messages/webpack5
warn - You have enabled the JIT engine which is currently in preview.
warn - Preview features are not covered by semver, may introduce breaking changes, and can change at any time.
<w> [webpack.cache.PackFileCacheStrategy] Skipped not serializable cache item 'Compilation/modules|C:\Users\J.Andrew\Documents\WebDev\nextjs-boilerplate\node_modules\next\dist\compiled\css-loader\cjs.js??ruleSet[1].rules[2].oneOf[7].use[1]!C:\Users\J.Andrew\Documents\WebDev\nextjs-boilerplate\node_modules\next\dist\compiled\postcss-loader\cjs.js??ruleSet[1].rules[2].oneOf[7].use[2]!C:\Users\J.Andrew\Documents\WebDev\nextjs-boilerplate\node_modules\next\dist\compiled\resolve-url-loader\index.js??ruleSet[1].rules[2].oneOf[7].use[3]!C:\Users\J.Andrew\Documents\WebDev\nextjs-boilerplate\node_modules\next\dist\compiled\sass-loader\cjs.js??ruleSet[1].rules[2].oneOf[7].use[4]!C:\Users\J.Andrew\Documents\WebDev\nextjs-boilerplate\styles\globals.scss': No serializer registered for CssSyntaxError
<w> while serializing webpack/lib/cache/PackFileCacheStrategy.PackContentItems -> webpack/lib/NormalModule -> webpack/lib/ModuleBuildError -> CssSyntaxError
error - ./styles/globals.scss:1:1
Syntax error: Unknown word
wait - compiling...
error - ./styles/globals.scss:1:1
Syntax error: Unknown word
Here is global.scss mentioned in the error. When I try to removed the tailwind imports, it compiles without a problem. But I needed those in order for tailwind to work.
#import '~tailwindcss/base';
#import '~tailwindcss/components';
#import '~tailwindcss/utilities';
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
My tailwind.config.js
module.exports = {
mode: 'jit',
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
important: true,
theme: {
container: {
center: true,
padding: '1.5rem',
},
extend: {
colors: {
// 'nav-bg': '#383E4C',
},
},
},
variants: {
extend: {},
},
plugins: [require('#tailwindcss/forms')],
}
And my postcss.config.js which is default
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Please help...
Today I had the same problem. My code was working just fine, until I created a React component that had a function that uses querySelector, something like this:
const handleSomething = () => {
const x = 150;
let activeSlide = document.querySelector('.slide');
activeSlide.classList.add(`-translate-x-[${x}]`);
}
It seemed that the -translate-x-[${x}] statement was causing the bug. However, after removing this line, the problem didn't go away. I tried to delete the "node_modules" and ".next" folders and reinstall the dependencies, but nothing seemed to work.
I don't know what caused it, but the only way I could get the application to run again was to go back to the previous commit (with a git reset --hard HEAD - WARNING: be careful with this command because you loose all uncommitted changes, but that was my intention) and delete that component (the file itself). Even a simple copy and paste of the contents of this file, with most of the "weird" lines commented out (this function, basically), would make the error come back. Literally nothing else seemed to work for me.
It probably doesn't answer your question, but I hope it can help someone who is facing the same problem, until no better solution comes up.
As Renato mentioned in his answer, it seems dynamically constructing tailwind class names returns this error.
This is explained in the tailwind docs here:
Dynamic class names
As outlined in Class detection in-depth, Tailwind doesn’t actually run your source code and won’t detect dynamically constructed class names.
❌ Don't construct class names dynamically
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
✔️ Always use complete class names
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
Also from this documentation about how tailwind detects CSS class names:
The most important implication of how Tailwind extracts class names is that it will only find classes that exist as complete unbroken strings in your source files.
If you use string interpolation or concatenate partial class names together, Tailwind will not find them and therefore will not generate the corresponding CSS
Therefore to dynamically set a CSS property of an element, using the inline style provided by React.js would be the best way to do it. For example:
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
}
I had a similar issue that happened when I tried to add a preview of the filed I just choosed.
For that I used the URL object like that :
image.src = URL.createObjectURL(picture)
I just remove this part of my component the error was gone.

Referencing css class in React component when it has -

hi if I have a css class imported into my React component as below:
import classes from './stylesheet.css'
How can I reference it, if the class has - in it?
<div className={classes.Content}></div>
Like for example if the class name is Content-LI if it is, how can I reference it? I mean reference it using classes in the above, it threw me error when I tried to reference it using
<div className={classes.Content-LI}></div>
It says undefined LI, any help please? thank you.
try <div className={classes["Content-LI"]}></div>
If you are trying to use modular css structure, First you need to configure css module in webpack.
Find the module block from webpack config and add this line:
{
test: /\.css$/,
loader: 'style-loader'
}, {
test: /\.css$/,
loader: 'css-loader',
query: {
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
Then you can import css to your component like this:
import styles from './app.css';
<div className={styles.app}>
<p>This text will be blue</p>
</div>
I have found this for more information. Hope its help for you :)

css-loader returns undefined on importing css file

I have a css file named App.css which has css like
.abcd {
margin-top: 50px !important;
color: red !important;
}
I am importing this and using in my react app like
import styles from './App.css'
console.log("style", styles) // it gives object.
<div className={"ui equal width grid " + styles.abcd}>
</div>
Here styles.abcd is showing undefined but when I console styles it gives object with that classname on array.. I dont know what is missing here.
My webpack conf looks like:
{
test: /\.css$/,
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]',
},
},
I have also tried
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
Nothing is working. Why is it giving undefined ?
Similar problem answered here: CSS Modules class names undefined
In the docs, it's mentioned that you have to name the file as follow :
[name].module.css
Try renaming the file to : Catalog.module.css and import it like this :
import styles from './Catalog.module.css';
In your case you should name it App.module.css and import as such.
Your code is :
import styles from './App.css'
console.log("style", styles) // it gives object.
<div className={"ui equal width grid " + styles.abcd}>
</div>
Here it should have been :
<div className={`ui equal width grid ${styles.abcd}`}>

webpack + mobx + React : require an observable array of images

I am using mobX to store arrays of images for a gallery like this :
#observable peopleThumb = [
'src/assets/photos/people/thumbs_1.jpg',
'src/assets/photos/people/thumbs_2.jpg',
'src/assets/photos/people/thumbs_3.jpg',
etc...
]
Where I have my thumbnails array, the full images array and the mobile images array.
Then I map through the array and use them either as background images or in an image tag in my components (urls are passed down via props) :
<img src={url} />
or
background: `url(${url}) no-repeat center center`,
But when I build for prod since the images are not required and the urls stays like src/assets/photos/people/' they obviously won't be displayed
So I tried simply requiring them :
<img src={require(url)}/>
and
background: `url(${require(url)}) no-repeat center center`
But I get an error :
Uncaught Error: Cannot find module 'src/assets/photos/people/full_3.jpg'.
I tried changing the path in my store to a relative path :
#observable peopleThumb = [
'../assets/photos/people/thumbs_1.jpg',
'../assets/photos/people/thumbs_2.jpg',
'../assets/photos/people/thumbs_3.jpg',
etc...
]
And same error :
Uncaught Error: Cannot find module '../assets/photos/people/full_2.jpg'.
My webpack config for my output and the file loader :
output: {
path: "./build/",
filename: 'static/js/[name].[chunkhash:8].js',
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js'
},
{
test: /\.(jpg|png|svg)(\?.*)?$/,
loader: 'file',
query: {
name: 'static/media/[name].[hash:8].[ext]'
}
},
When I import single images via Import everything works fine but I am stuck when it comes to this observable. I also tried the require context but I honestly didn't really understood what I was doing and I think I should be able to simply require my images once mapped.
Thanks.

Resources