Tailwind CSS unresponsive to react state change - reactjs

This very simple toggle in react is not working with tailwind CSS. When I click the "toggle color" button, the className of the div changes between bg-[#222222] and bg-[#DDDDDD], but the div never actually colors -- it has the className, but tailwind doesn't seem to be able to fill in the background property.
import React, { useState } from "react";
function App() {
const [color, setColor] = useState('222222')
const toggleColor = () => color === '222222' ? setColor('DDDDDD') : setColor('222222');
return (
<div className='h-screen w-full flex flex-col justify-center items-center'>
<button onClick={toggleColor}>Toggle Color</button>
<div className={`bg-[#${color}] w-[100px] h-[100px]`}></div>
</div>
)
}
export default App;
To clarify, if I store the full string 'bg-[#2222222]' or 'bg-[#DDDDDD]' and then drop that directly into the className, it works. (AKA, in the below code segment, the div renders with the proper color and toggles properly:
import React, { useState } from "react";
function App() {
const [color, setColor] = useState('bg-[#222222]')
const toggleColor = () => color === 'bg-[#222222]' ? setColor('bg-[#DDDDDD]') : setColor('bg-[#222222]');
return (
<div className='h-screen w-full flex flex-col justify-center items-center'>
<button onClick={toggleColor}>Toggle Color</button>
<div className={`${color} w-[100px] h-[100px]`}></div>
</div>
)
}
export default App;
However, in the larger context of the application I'm hoping to build, this doesn't do me much good. It would be much more helpful to simply store the color. It's unclear to me why the first way is not working, considering that in both cases, the full string is successfully dropped into the className. Any ideas?

This is the expected behaviour of Tailwind and the reason is explained here: https://tailwindcss.com/docs/content-configuration#class-detection-in-depth
One option would be to use a safe list which is described here: https://tailwindcss.com/docs/content-configuration#safelisting-classes

Another solution for an exception like this is to ignore Tailwind and add a dynamic style to the element. For example:
<div className={'w-[100px] h-[100px]'} style={{'background-color': `#${color}`}}></div>
This might be more flexible than safelisting Tailwind classes, if you have a lot of colors or if you don't know the color value until runtime.

Related

TailwindCSS styles not rendered when applied dynamically in NextJs

To set the background cover using TailwindCSS I have extracted the color from bookId (10 digit number) inside useEffect. The color gets updated and component re-renders with the updated color value but the background color on the rendered page is still the same of its parent div.
const colors = [
'from-red-500',
'from-orange-500',
'from-yellow-500',
'from-green-500',
'from-cyan-500',
'from-blue-500',
'from-indigo-500',
'from-violet-500',
'from-purple-500',
'from-pink-500',
]
function BgCover(props) {
const [color, setColor] = useState(null)
const router = useRouter()
useEffect(() => {
const id = router.query.bookId
const index = id.slice(-1) //extract the index from bookId
const bgColor = colors[index]
setColor(bgColor)
}, [])
return (
<Fragment>
{color ? (
<div className='flex-grow scrollbar-hide select-none relative'>
<div className={`bg-gradient-to-b ${color} to-black`}>
<section
className={`flex flex-col md:flex-row items-center justify-center p-4`}>
{props.children}
</section>
</div>
</div>
) : (
<p className='text-2xl'>Loading..</p>
)}
</Fragment>
)
}
But when I replace the color variable with a color value (say 'from-red-500') then the background color is visible in the rendered page.
I have also tried to replace the setColor code in useEffect with getStaticProps but the static version of the code cannot solve this problem (when color varible is used).
Thanks for any help.
This is a known issue with tailwindcss and dynamic classes, because the class is applied after rendering so its effect won't be generated by tailwind unless there was another element in that had the same class as a static class.
So, you can use tailwind "safelist" to resolve this.
In your tailwind.config, define a safelist array of all tailwind classes that you need to be generated and that don't exist in your code as static classes.
tailwind.config.js:
module.exports = {
content: [
'./pages/**/*.{html,js}',
'./components/**/*.{html,js}',
],
safelist: [
'from-red-500',
'from-orange-500',
'from-yellow-500',
'from-green-500',
'from-cyan-500',
'from-blue-500',
'from-indigo-500',
'from-violet-500',
'from-purple-500',
'from-pink-500',
]
// ...
}
Now this classes will always get generated so when you apply them dynamically, they will change accordingly.
Note that you need to restart your server after adding to the safelist.
Source
Another manual solution is to make a hidden element and add all your needed classes to it so they will be generated even if you get them dynamically after render
<div className="hidden from-red-500"></div>
but safelist is better I think

Conditionally set background color in React component with Tailwind CSS

I am trying to use a hex color code passed through props to set the background color of a div. These are one-off colors that are generated dynamically, so cannot be added as a theme extension in tailwind.config.
I thought a template literal would be the best way to achieve this, but have not been able to get this to work with arbitrary color values in Tailwind CSS.
interface Props {
color: string;
}
const ColorSwatch = ({ color }: Props) => {
return (
<div className="flex flex-col gap-1 p-2">
<div
className={`h-20 w-20 border border-gray-400 shadow-md bg-[${color}]`}
></div>
<p className="text-center">{color}</p>
</div>
);
};
export default ColorSwatch;
Pasting the hex color code directly into the className list produces expected results, but trying to use the prop value in a template literal results in a transparent background (no background effect applied).
Looking for advice on how to correct this or different approaches to dynamically setting background color with a hex code passed through props.
Unfortunately, Tailwind requires the color to be hardcoded into the className prop as it cannot compute arbitrary styles from dynamic className values.
Your best bet would be to set the background color using the style prop as shown below;
interface Props {
color: string;
}
const ColorSwatch = ({ color }: Props) => {
return (
<div className="flex flex-col gap-1 p-2">
<div
className="h-20 w-20 border border-gray-400 shadow-md"
style={{backgroundColor: color}}
></div>
<p className="text-center">{color}</p>
</div>
);
};
export default ColorSwatch;
You can look here and here to read more on how Tailwind generates arbitrary styles.

Use React variables in Tailwind class names

I've just recently picked up React and Tailwind for a project, and I am still very much a beginner. I wanted to make an element have a background image as a custom class variable, something like this:
<div className="bg-[url(`https://example.com/${variable}.png`)]"></div>
But as Tailwind purges classes, would this somehow be possible? I hope I'm not missing anything, but it doesn't seem doable to me right now
Tailwind has a feature called "safelisting" which may provide you with the functionality you need – see here in the docs.
That said:
1 - There's nothing wrong with using both className and style props on the same element – you can have e.g. <div className="w-full" style={`background: url(https://example.com/${variable}.png`}>
2 – Depending on what flavor of React you are using, your might be better off using a component like Next.js's Image to optimize your images instead of setting background via CSS. Below is how I do it:
import Image from "next/image";
export default function MyBackground(props) {
return (
<div
className="z-0 absolute inset-0 w-full h-full flex justify-center items-center"
>
<Image
placeholder="blur"
alt={props.alt}
src={props.src}
className="h-full w-full object-cover"
/>
</div>
);
}
You can define a different variable for this. In that way, you can manipulate the value accordingly and use that variable to set the styling properties of that element. The code will look something like this:
const YourComponent = () => {
//the variable named variable will hold the dynamic value as per your need
let elementStyle = "bg-[url(`https://example.com/" + variable + ".png";
return (<>
<div className={elementStyle}"></div>
</>
);
}

Tailwind not working when using variables (React.js)

currently have been facing this issue using tailwind and making rehusable react components where you can pass as a prop some styles as tailwind classes. The actual problem is with the "pb-{number}" propierty. I can pass it this way and will work fine. This also happens with "border-{number}" property, but someway it accepts border-2 and border-4 (only these).
import './button.css'
export default function Button({
color = "orange",
inset = "pb-3", <--- this will work
border = "border-8",
className,
onClick,
link
, ...props}){
return (
<div onClick={onClick}
className={`btn-${color} ${border}
${className} ${inset}`}> <--- this will work
<div>
{props.children}
</div>
</div>
But if I try to make it cleaner so a person who don't use tailwind only has to pass a value (like the example below) it wont work.
import './button.css'
export default function Button({
color = "orange",
inset = "1", <--- this
border = "4",
className,
onClick,
link
, ...props}){
return (
<div onClick={onClick}
className={`btn-${color} border-${border}
${className} pb-${inset}`}> <--- this wont work
<div>
{props.children}
</div>
</div>
)
}
Sincerely I have no idea why is this happening. Hope someone with more experience can clarify my doubt.
Thanks in advance.
In tailwind you can't use dynamic class naming like bg-${color}, though we can mock it to be that, but it is not preferred. Because Tailwind compiles its CSS, it looks up over all of your code and checks if a class name matches.
For your approach you can try this.
const Button = () => {
const color = "red-500";
const inset = "3";
const border = "border-8";
return <div className={`bg-${color} ${border} pb-${inset}`}>Hello</div>;
};
export default Button;
Output with proper padding is applied
But try avoiding this workaround, as it is not recommended by the Tailwind CSS.

React: State of a variable changes, but UI Icon dosen't get updated

So I tried creating a simple navbar that would have its buttons controlled by useState. However I have a problem where the button icon color wont update even though the state of the variable that controls it changes.
Now, I did some testing and and added text into the icon component (not show here) and made it so it was controlled by the same state as the color on the icon is now. And for some reason when I did that the state and the text inside the component both changed correctly. Could anyone provide an explanation on why that happens? Because to me it seems like I've misunderstood how react binds things to states and controls them.
Navigation bar component
import NavButton from "./NavButton"
import { useState } from "react";
function NavBar(){
const [buttons, setButtons] = useState([
{id:1, name:"Orders", icon:"bx:bx-dollar-circle", active:false},
{id:2, name:"Menu", icon:"ic:round-restaurant-menu", active:false},
{id:3, name:"Leave", icon:"uil:exit", active:false}
]);
const toggleButton = (id) => {
setButtons(buttons.map(button => (
button.id === id ? {...button, active:!button.active} : {...button, active:false}
)))
}
return (
<div className="h-1/6 bg-white border-b-lebo flex flex-row justify-around">
<>
{buttons.map((button) => (<NavButton button={button} key={button.id} onToggle={toggleButton}/>))}
</>
</div>
)
}
export default NavBar;
Navigation button component
import Icon from "./Icon";
function NavButton({button, onToggle}){
return (
<button onClick={() => onToggle(button.id)} className={`font-bold text-gray-500 flex flex-col items-center justify-center flex-grow w-5 hover:bg-gray-100`}>
<p className="self-center">{button.name}</p>
<Icon icon={button.icon} name={button.name} color={button.active ? "#454545" : "#8b8b8b"}/>
</button>
)
}
export default NavButton;
Icon component
function Icon({icon, color, name}) {
return (
<div>
<span color={color} className="iconify h-10 w-auto self-center" data-icon={icon}></span>
</div>
)
}
export default Icon
I solved my problem by creating 2 different Icon components.
Icon and IconDark and conditionally rendering them inside the NavButton component.
Not sure if it is the "correct" way of doing things but it got the job done.
I'm going to guess the reason why it didn't render the colors correctly earlier is because of the attribute "color" inside the component. I think JSX just took it in as another prop and did nothing with it after the first render of the element.
edit 1: nvm it definitely didn't get the job done. At least not well enough. The icon swap in the render isn't fast enough so it causes the user to see the icon swap.
edit 2: This article held the answer that I needed.
https://dev.to/abachi/how-to-change-svg-s-color-in-react-42g2
It turns out that to change an svg color with react you need to set the initial fill (or for me color) value inside the svg component to "current" and then pass the real value in from the parent element conditionally.
Long story short - Controlling SVG values is a little different to controlling text values in react.

Resources