React and Tailwind CSS: dynamically generated classes are not being applied - reactjs

I'm just learning React and Tailwind CSS and had a strange experience with CSS grid using Tailwind classes. I've made the buttons for a calculator, with the last Button spanning two columns:
App.js:
export default function App() {
return (
<div className="flex min-h-screen items-center justify-center bg-blue-400">
<Calculator />
</div>
);
}
Calculator.js
import { IoBackspaceOutline } from "react-icons/io5";
export const Calculator = () => {
return (
<div className="grid grid-cols-4 grid-rows-5 gap-2">
<Button>AC</Button>
<Button>
<IoBackspaceOutline size={26} />
</Button>
<Button>%</Button>
<Button>รท</Button>
<Button>7</Button>
<Button>8</Button>
<Button>9</Button>
<Button>x</Button>
<Button>4</Button>
<Button>5</Button>
<Button>6</Button>
<Button>-</Button>
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
<Button>+</Button>
<Button>0</Button>
<Button>.</Button>
<Button colSpan={2}>=</Button>
</div>
);
};
const Button = ({ colSpan = 1, rowSpan = 1, children }) => {
return (
<div
className={`col-span-${colSpan} row-span-${rowSpan} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
This doesn't work (tested in Chrome):
Now here comes the weird part. I replaced the returned JSX from the App component with HTML from a Tailwind tutorial and deleted it again.
<div className="bg-blue-400 text-blue-400 min-h-screen flex items-center justify-center">
<div className="grid grid-cols-3 gap-2">
<div className="col-span-2 bg-white p-10 rounded">1</div>
<div className="bg-white p-10 rounded">2</div>
<div className="row-span-3 bg-white p-10 rounded">3</div>
<div className="bg-white p-10 rounded">4</div>
<div className="bg-white p-10 rounded">5</div>
<div className="bg-white p-10 rounded">6</div>
<div className="col-span-2 bg-white p-10 rounded">7</div>
<div className="bg-white p-10 rounded">8</div>
<div className="bg-white p-10 rounded">9</div>
</div>
</div>
After I Ctrl-Z'd a bunch of times, so I had only the previous code, my button suddenly spans two columns as intended:
I checked to make sure that there were no changes in the code:
My friend even cloned my repo, followed the same steps and got the same result.
He suspects that it has something to do with the variable classNames in my Button component with regards to Tailwind's JIT compiler, but none of us can pinpoint the error.
Am I using variable CSS classes wrong?
This has been a WTF moment. What could be the reason for this?

The CSS file generated by Tailwind will only include classes that it recognizes when it scans your code, which means that dynamically generated classes (e.g. col-span-${colSpan}) will not be included.
If you only need to span 2 columns, you could pass boolean values which will trigger the addition of a full col-span-2 or row-span-2 utility class to be added:
const Button = ({ colSpan = false, rowSpan = false, children }) => {
return (
<div
className={`${colSpan ? 'col-span-2' : ''} ${rowSpan ? 'row-span-2' : ''} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
Otherwise, you could pass the values as classes to the Button component:
<Button className='col-span-2 row-span-1'>=</Button>
const Button = ({ className, children }) => {
return (
<div
className={`${className} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
More information: https://tailwindcss.com/docs/content-configuration#dynamic-class-names

Another tricky solution that worked for me is to use variable with forced type of the possible className values (in typescript) like :
export type TTextSizeClass =
'text-xl' |
'text-2xl' |
'text-3xl' |
'text-4xl' |
'text-5xl' |
'text-6xl' |
'text-7xl' |
'text-8xl' |
'text-9xl'
;
...
const type : number = 6 ;
const textSizeClass : TTextSizeClass = type != 1 ? `text-${type}xl` : 'text-xl';
...
<div className={`font-semibold ${textSizeClass} ${className}`}>text</div>

As Ed Lucas said:
The CSS file generated by Tailwind will only include classes that it recognizes when it scans your code, which means that dynamically generated classes (e.g. col-span-${colSpan}) will not be included
But 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.

Related

MUI-Theming with Tailwind in className [duplicate]

This question already has an answer here:
Conditionally set background color in React component with Tailwind CSS
(1 answer)
Closed last month.
I want to use MUI theming in my otherwise Tailwind-styled page so I can centrally handle the color palettes for dark and light mode since I found the decentral dark and light mode theming provided by Tailwind not very practicable in case I want to change colors.
My approach is to use the Tailwind syntax for arbitrary values and pass the hex code from the theme.js color palette.
the Tailwind arbitrary syntax is:
bg-[#hex]
e.g.:
bg-[#50d71e]
which just works fine in normal classNames, e.g.:
<div className={`bg-[#50d71e]`}>
<div/>
gives me a green background (#50d71e) in that container.
On the other side, I have a mui-themed template page. Here I can reference colors as follows:
import {useTheme} from "#mui/material";
import { tokens } from "../../theme";
const AnyName = () => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
return (
<Box backgroundColor={colors.grey[400]}></Box>
<Box sx={{backgroundColor: colors.grey[500],}}></Box>
}
export default AnyName;
And again, it just works fine.
Now, if I want to fuse these two approaches, I am struggling to query the hex code out of the const colors = tokens(theme.palette.mode);.
The theme.js holds all color values as hex, so I think this should be possible. Here is a look inside:
export const tokens = (mode) => ({
...(mode === 'dark'
?
{
grey: {
900: "#141414",
800: "#292929",
700: "#3d3d3d",
600: "#525252",
500: "#666666",
400: "#858585",
300: "#a3a3a3",
200: "#c2c2c2",
100: "#e0e0e0",
},
} : {
grey: {
100: "#141414",
200: "#292929",
300: "#3d3d3d",
400: "#525252",
500: "#666666",
600: "#858585",
700: "#a3a3a3",
800: "#c2c2c2",
900: "#e0e0e0",
},
}),
});
** What I have tried: **
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
return (
<div
className={`bg-[${colors.grey[400]}]`}
></div>
);
};
export default AnyName;
This doesn't work. I guess it is a matter of scoping, but I am not sure, and I honestly do not know what I am doing.
So I tried a few more things like:
className={`bg-[{colors.grey[400]}]`}
Which doesn't seem to reference colors at all.
And:
className={`bg-[colors.grey[400]]`}
Which also does not reference colors.
All three approaches result in a transparent background.
Now I am here, hoping for your help. Thanks.
I suspect this to work, But you are not able to see the background color because the there is no children for this div, which makes it height of 0
Try adding height:
<div className={`bg-[${colors.grey[400]}] h-24`}/>
Thanks for your answer. Up there is not the complete code. I tried to keep it short for the post. There are children, and if I set the background using normal Tailwind syntax, e.g., bg-[#50d71e], it works as expected. Here is the full return statement, unmodified:
return (
<div
className={`bg-[#50d71e] shadow-lg rounded-lg p-0 lg:p-8 pb-12 mb-8`}
>
<div className="relative overflow-hidden shadow-md pb-80 mb-6">
<img
src={post.featuredImage.url}
alt={post.title}
className="object-top absolute h-80 w-full object-cover shadow-lg rounded-t-lg lg:rounded-lg"
/>
</div>
<h1
className="transition duration-700 text-center mb-8 cursor-pointer
hover:[text-cyan-400] text-3xl font-semibold"
>
<Link href={`/post/${post.slug}`}>{post.title}</Link>
</h1>
<div className="block lg:flex text-center items-center justify-center mb-8 w-full">
<div className="flex items-center justify-center mb-4 lg:mb-0 w-full lg:w-auto mr-8">
<img
alt={post.author.name}
height="30px"
width="30px"
className="align-middle rounded-full"
src={post.author.photo.url}
/>
<p className="inline align-middle text-gray-700 ml-2 text-lg">
{post.author.name}
</p>
</div>
<div className="font-medium text-gray-700">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 inline mr-2 text-cyan-600"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span>{moment(post.createAt).format("DD. MMM, YYYY")}</span>
</div>
</div>
<p className="text-center text-lg text-gray-700 font-normal px-4 lg:px-20 mb-8">
{post.excerpt}
</p>
<div className="text-center">
<Link href={`/post/${post.slug}`}>
<span className="transition duration-500 transform hover:-translate-y-1 inline-block bg-cyan-500 text-lg font-medium rounded-full text-white px-8 py-3 cursor-pointer">
Continue Reading
</span>
</Link>
</div>
</div>
);
Since the VS Code autocompletion works as intended when using the $-approach, I guess maybe it is not or not only a matter of scope.
I messed around a bit more and found something strange.
In the following example I will be using colors.grey[900] which is #141414.
If I manually enter bg-[#141414], the background will be dark-grey (obviously).
If I then enter bg-[${colors.grey[900]}], it works just fine.
So I guessed, maybe that's just some caching.
I double-checked --> chrome --> F12 --> disable cache --> page refresh --> still works fine.
I double-checked again, differently by changing the hex to a different value, e.g. bg-[${colors.grey[800]}] --> doesn't work. Changing it back to bg-[${colors.grey[900]}] --> works again.
Checked it on a different Browser which always clears cache and never loaded the page before: still works.
Restart Server: and bg-[${colors.grey[900]}] also does not work anymore.
Steps to reproduce:
doesn't show grey background
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[900];
return (
<div
className={`bg-[${hex}]`}
></div>
);
};
export default AnyName;
shows grey Background
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[900];
return (
<div
className={`bg-[#141414]`}
></div>
);
};
export default AnyName;
shows grey Background
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[900];
return (
<div
className={`bg-[${hex}]`}
></div>
);
};
export default AnyName;
doesn't show grey background (changed colors.grey to 800)
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[800];
return (
<div
className={`bg-[${hex}]`}
></div>
);
};
export default AnyName;
shows grey background even in different browser without caching
const AnyName = ({ anyProp }) => {
const theme = useTheme();
const colors = tokens(theme.palette.mode);
const hex = colors.grey[900];
return (
<div
className={`bg-[${hex}]`}
></div>
);
};
export default AnyName;
So I guess there must be some weird server-side binding issue that I overcome by once providing directly the correct value and afterwards it seems to be cached on server-side and works. Once I restart the server, everything is gone and I have to go through steps 1,2,3 to make it work again.
Any ideas? Workarounds also welcome.
edit:
I found this thread:
JIT tailwindcss using variable in bg-[] not rendering color
Unfortunately, I did not find it before, even after reviewing related threads.
This sheds a lot of light on the issue.

remove file function not allowing removed file to be added again, only after selecting another file - ReactJS

I made a function in my project that allows me to add and remove a file that will be uploaded, the intended function of adding and removing is implemented, however, when I try adding the file that I previously added (for the testing of adding and removing same files because it is a possible action of the user) it is not showing up. The removed file can only be added again if I select a different file first for preview then remove that different selected file, and selecting again the first removed file; which is not a good ux.
remove file function, I turned it into a global function because different pages uses this.
`
export const handleRemoveFile = (setSelectedFile) => {
setSelectedFile({ fileName: undefined, fileUrl: undefined, isImage: false });
};
`
attachment input component, (if you need to see it), I turned it into a global function because different pages uses this.
`
import React from "react";
export default function AttachmentInput(props) {
return (
<label
className={`${
props.hasSubmitted && "hidden"
} custom-input w-full custom-flex bg-white cursor-pointer text-sm font-Poppins font-semibold hover:shadow-md
tablet:text-base
laptop-l:text-lg`}
htmlFor={props.htmlFor}
>
<input
className="hidden"
type="file"
name={props.name}
id={props.id}
onChange={(e) => {
props.onChange1(e.target); // post data for upload
props.onChange2(e); // for file preview
}}
/>
<div>{props.selectedFile.fileUrl ? props.secondaryLabel : props.primaryLabel}</div>
</label>
);
}
`
**attachment preview **component, (if you need to see it), I turned it into a global function because different pages uses this.
import React from "react";
import logo from "../../";
export default function AttachmentPreview(props) {
return (
props.selectedFile.fileUrl && (
<div className="custom-flex w-full h-fit">
<div
className={
props.className
? `${props.className}`
: "custom-flex flex-col bg-white custom-light-border py-3 px-0"
}
>
{props.selectedFile.isImage ||
props.selectedFile.fileUrl.endsWith(".jpg") ||
props.selectedFile.fileUrl.endsWith(".png") ? (
<img
className={`max-h-36 rounded-lg`}
src={props.selectedFile.fileUrl}
alt="selected file"
/>
) : (
<img className="max-h-16" src={logo} alt="holder" />
)}
<div className="custom-divider my-4 w-full" />
<div className="font-Poppins font-light">
{props.selectedFile.fileName
? props.selectedFile.fileName
: props.postData.file_name
? props.postData.file_name
: "Current Uploaded File"}
</div>
</div>
</div>
)
);
}
usage of remove file function
{selectedFile.fileUrl && (
<CancelButton
onClick={() => file_fns.handleRemoveFile(setSelectedFile)}
label={"REMOVE FILE"}
/>
)}

Why Tailwind some classes don't work in React

I want to use Tailwind to style my app in React but some classes are not working, how can I fix it? And what would affect it?
function NavBar({ title }) {
return (
<nav className="navbar mb-12 shadow-lg bg-neutral text-neutral-content">
<div className="container mx-auto">
<div className="flex-none px-2 mx-2">
<AiFillGithub />
</div>
</div>
</nav>
);
}
but on web page nothing changed.
I sorted out the problem, in file tailwind.config.js I had:
content:["./src/**/*.{html,js}", "./components/**/*.{html,js}"]
and I should change for:
content: ["./src/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"]
I posted my solution just in case if someone will face the same problem

How do I put my button on the same line as my Component Title?

how can I modify my Title.jsx code in order to have the possibility to give to this component another component i.e a button inside my Title component ?
Because if I want to display a button in the same line (on the right) as my title it's not possible, it's displayed under it (see picture). So do I have to go through this way or just using tailwindcss (which is not working if do right-0 left-0) ??
export default function Title({ className, title, children }) {
return (
<Format className={className}>
<h1>{title}</h1>
<hr />
{children}
</Format>
)
}
export default function Format({ className, children }) {
return (
<div className={`${className ?? ''}`}>
{children}
</div>
)
}
export default Display() {
return (
<Title>
<button> Button </button>
</Title>
}
Here is my code
Here the picture:
Maybe you should try to change order and encapsulate items inside Title's component:
export default function Title({ className, title, children }) {
return (
<Format className={className}>
<Format className="flex">
<h1 className="flex-1">{title}</h1>
{children}
<Format/>
<hr/>
</Format>
)
}
And add className what you want for new div wrapper, for example, flex and h1 title with flex-1 to expand it. If children is null, title will expand fullWidth.
I've updated answer and assume you will use div wrapper with Format component.
Guide in tailwind
You can use the flex property to achieve the same layout that you want. The idea is to keep the Title and Button in the same div with each of them are also enclosed within their separate div or span. And add flex justify-between items-center in the main div.
<script src="https://cdn.tailwindcss.com"></script>
<div class="p-4 flex items-center bg-slate-200 justify-between">
<div><h4 class="font-semibold"> My Title</h3></div>
<div><button class="bg-purple-700 text-white px-4 py-2 rounded-md">Click</div>
</div>

Tailwind HeadlessUI toggle switch with label in between

Based on the toggle switch in the headlessui, I have like to place a text in between the switch. I came out with something like below but the text simply follow the inner rounded button. Is there a way that I can place the text(enable/disable) on the left or right hand side of the inner button?
https://headlessui.dev/react/switch
import React, { useState } from 'react'
import { Switch } from '#headlessui/react'
const App = () => {
const [enabled, setEnabled] = useState(false)
return (
<Switch.Group>
<div className='flex items-center'>
<Switch
checked={enabled}
onChange={setEnabled}
className={`${
enabled ? 'bg-blue-600' : 'bg-gray-200'
} relative inline-flex items-center h-12 rounded-full w-48 transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500`}
>
<span
className={`${
enabled ? 'translate-x-32' : 'translate-x-1'
} inline-block w-12 h-11 transform bg-white rounded-full transition-transform`}
>
{enabled ? 'Enable' : 'Disable'}
</span>
</Switch>
</div>
</Switch.Group>
)
}
export default App
You could give the span absolute class then replace translate-x-32 with left-0 and translate-x-1 with right-0
then add another span for the text and then add flex justify-center items-center to the Switch to justify the text in the center look here

Resources