I have seen a lot of threads on the topic, but nothing seems to work for me the way I want it. I have a React component which takes in className as a prop. I also have a HOC which takes in a component and default style, and returns a component which takes in a new style, as such:
function withCombinedStyles(WrappedComponent, defaultClassName) {
return ({className: newClassName, ...rest}) => {
const className = combineClassNames(newClassName, defaultClassName);
return <WrappedComponent className={className} {...rest} />;
};
};
Currently, combineClassNames just concatenates the strings. I want to make it so that the newClassName always overrides the clashing styles with the defaultClassName but keeps the old ones. Currently, one of the styles seems to override the other but without apparent order - it doesn't matter how I put defaultClassName and newClassName. How can I do that? Currently, I use CSS modules but the project is still small enough for me to be able to rewrite the styles with another technology if that would achieve the desired result and CSS modules cannot do that (though I would prefer to use CSS modules and a React-specific solution with them would work since).
I would use !important but that would mean that I can never add a third className and it could be challenging to extend the styling in the future.
If you were doing CSS in JS then the order would matter, but because you're just combining CSS class names what really matters is specificity. Say newClassName has declarations that are less specific than the ones in defaultClassName - then the more specific declarations in defaultClassName will win over. You can probably fix your CSS by just ensuring the statements you want to 'win' are more specific.
Check out calculators like this one
If you went with inline CSS-in-JS styles I think something like this might work:
function withCombinedStyles(WrappedComponent, defaultStyles) {
return ({styles: newStyles, ...rest}) => {
return <WrappedComponent style={{...defaultStyles, ...newStyles}} {...rest} />;
};
};
Check this out < my library, which effectively does .logo.logo under the hood, but in a neater way.
Related
I know you can wrap your react app with <ThemeProvider /> and you can set a variable for the theme and access it like so:
const Text styled`
color: ${((props) => props.theme.red)};
`
But it's it CSS itself have the variable feature?
You can just do
:root {
--red: tomato;
}
.text {
color: var(--red);
}
is it ok to mix them? or stick to either styled-components or CSS?
Yes it is OK. Those 2 technologies have nothing to do with each other and are not conflicting.
As long as your components can inherit the CSS variables (AKA custom properties) they will work.
Just make sure they are really inherit.
Clearly defining variables on the root element selector (html) will make them available everywhere, but sometimes you don't want global variables, but more local, per page/area/component, so you need to structure you code taking that into account.
The real power of CSS variables is in their inheritance (unlike SCSS variables which are injected/replaces during the build-process).
The power of styled-components is isolation & the ability to share code with javascript. This does hinder their ability to inherit CSS variables defined at parents-level.
You need not bother yourself with this question at all, and simply ignore the fact you are using styled-components. All that matters is the HTML structure, which how inheritance works.
I Googled things for you:
https://medium.com/fbdevclagos/how-to-leverage-styled-components-and-css-variables-to-build-truly-reusable-components-in-react-4bbf50467666
https://betterprogramming.pub/7-ways-to-inherit-styles-using-styled-components-69debaad97e3
https://dev.to/arnonate/using-css-variables-to-tame-styled-component-props-2f9o
So a little disclaimer: I am completely and utterly self taught. Bear over with me if I'm being a clown.
Anyways, I am currently working on a some platform and in need for a dropdown functionality. That's simple right? Just use HTML5 select tag. However option tags can't be styled :>
So onwards to build my own. The HTML5 select tag uses keyboard input (up/down/enter) for those with disabilities, and I thought I would implement that too. That did present a problem though: The :hover selector collided with my custom attribute, which I use to style keyboard selected items (&[data-selected=true] to be precise).
So onwards to implement my own :hover. And this is where my bewilderment starts.
const handleChildMouseOver = () => {
const items = Array.from(listItem.current?.children!); // The wonders of typescript XD
for (const item of items) {
if (item === event.target) {
item.setAttribute("data-selected", "true");
} else {
item.removeAttribute("data-selected"); // I'm removing the attribute, rather than toggling it, because I got components with 3 states: On, off, and default.
}
}
}
(...)
<ul css={css.list} /*emotion prop*/ data-toggled={toggled} /*parent state*/ onMouseOver={handleChildMouseOver}>
{children} // parent prop
</ul>
So it works as intended, which is fine. But I recall from my pre-react days that you should never manipulate the DOM in loops, as it causes repaints on every iteration. However when I look at the Dev Tools performance profiler, I barely see any "Paints", 8 or so, even when I'm switching hover targets like a madman. What I do see is one million "Composite layer". Oh, and as a bonus React doesn't re-render. Which is fine right? 'Cause I'm not really changing the state of anything, just adding some CSS.
So my question boils down to: Am I being bonkers or smart?
N.B.: I would love to share the actual component, but seeing as this is my first post on stackoverflow, I've got no clue how you do those fancy script tag. Well github is involved somehow, I know that much 🤔
So I'm migrating an app from CRA to NextJS and I have encountered an error for the .module.scss files of some components and pages:
Syntax error: Selector ":global(.label-primary)" is not pure (pure selectors must contain at least one local class or id)
I get this error for all the :global and :local css-module selectors. Based on what I have searched I can fix this issue by wrapping the selector in a class and editing the jsx aswell. but wouldn't that defeat it's purpose?
And how is this working on the CRA version of the app and not on NextJS?
EDIT:
One solution I have for this is moving :global() selectors to the global css files that are imported in _app.js but my question is that is there any way that we can have so these styles would be usable like they are right now ( :global(...) )?
No there isn't any solution as of yet other than overriding the webpack config itself. It was working in CRA because they probably have mode: local, while Next.js has pure.
I haven't tried overriding css-loader webpack config, so I am simply suggesting a workaround. Since, you are using SCSS, you can wrap your pseudo-global [1] styles like this:
.root :global {
.foo {
color: red;
}
}
Now wrap your component/page in a div and set the class as styles.root on that element. Then, on all the child elements you can directly set className="foo".
import styles from "../styles/index.module.scss";
const IndexPage = () => (
<div className={styles.root}>
<div className="foo">This text should be red!</div>
</div>
);
export default IndexPage;
Note that, you need to consider issues regarding specificity after this method, also this doesn't directly work with animations, you need to separate the keyframes and then make them global.
Demo Sandbox
[1]: This method doesn't make the styles truly global as the styles are still scoped. The class foo will work only when some parent has styles.root as class. This is preferrable only if you didn't intend to use your :global(.selector) from other components, and were using them just because you wanted to manipulate the class names using JS without the styles object.
If you want these to be truly global, add styles.root to document.documentElement in an useEffect hook like this:
import { useEffect } from "react";
import styles from "../styles/index.module.scss";
const IndexPage = () => {
useEffect(() => {
document.documentElement.classList.add(styles.root);
return () => {
document.documentElement.classList.remove(styles.root);
};
}, []);
return (
<div className="foo">
This text should be red, even if you put it in another component until the
page is same. If you want it across pages inject it in _app or _document.
</div>
);
};
export default IndexPage;
Demo Sandbox
PS: Injecting class to html in _app or _document is not exactly same as using a global stylesheet, as it may happen that you have multi-page application, then only the CSS of the components on a particular page will be requested because of automatic CSS code-splitting done by Next.js. If that's not the case and all your pages share same CSS, then there is no need to complicate things, just go with the conventional method of importing styles in _app.
I had the same problem, the right writing is
.root:global {
color:red
}
Another approach is to make a container, wrap it, and carry it out like follows:
import style from '../styles/style.module.css'
<div className={styles.container}>
<p>Hello World</p>
</div>
.container p {
font-size: 20px;
}
And yes, you only need to include your tags and conditions in the CSS file if you have a lot of them.
I'm building a design system where I put emphasis on the architecture - I want it to be a good developer experience.
Bootstrap is used in this system but also together with component-specific styles. I'm thinking I want CSS-in-JS because I like to write style in the actual component, not in a different CSS file.
I have an idea, something like this:
const componentSpecificStyle = `margin-bottom: 0rem;`
const globalBootstrapClass = ['card', 'card-body']
return <div someArgument={componentSpecificStyle + globalBootstrapClass} />
This way it would be standard, two types of classes, one argument on the element, less confusion for coming developers.
What I've tried
Looked into Styled-Components and Emotion, from what I can tell they don't help me achieve this. It's possible to do
const Component = styled.div`margin-bottom: 0rem;`
return <Component className={'card card-body'}
But it seems a bit clumsy to me.
React-Bootstrap would be a solution for this but I think I usually end up adding extra classnames anyway, so I won't abstract that away...
ex.
import { Card } from 'react-bootstrap'
const componentSpecificStyle = `margin-bottom: 0rem;`
return <Card.body style={{componentSpecificStyle}} />
or something similar... but as I said, what if I want to add another classname on top of this? then we are styling in three different ways, react-bootstrap, inline-ish style and className.
Question
Am I making it unnecessarily complex?
Any suggestions or thoughts on the matter are highly appreciated!
If you want to define CSS inside your components, you're going to need CSS-in-JS indeed.
Theoretically, you're allowed define inline styles like so:
const style = {
marginBottom: '0rem',
};
return <div style={style} />
but this doesn't scale well, all styles are inlined, and you don't get all the benefits of CSS-in-JS, like ability to use props, themes, style inheritance, and having your styles autoprefixed.
Example of what you've tried in pt 1 actually makes a lot of sense.
Maybe it isn't the absolute best idea to mix global class names with styled-components, so if you want to go all-in with styled-components, you could define some set of styles in styled-components only, and inherit them one in another, like so:
const Card = styled.div`
/* Your card styles go here */
`;
const CardBody = styled(Card)`
/* Your card body styles go here */
/* (in your example you apply card and card-body to the same element, */
/* so I assume card-body is a "variant" of card) */
`;
const Component = styled(CardBody)`
margin-bottom: 0rem;
`;
return <Component />
This way, you have all your styles managed by styled-components, and you don't need to worry about global styles overwriting something you wrote in styled-components - because Component styles will be more important than CardBody styles, and CardBody styles will be more important than Card styles.
The best solution I've come up with so far is using Emotion and their css-prop on elements. https://emotion.sh/docs/css-prop
This way I can write code like it's inline-css but the end result is a class on the element, which I think is neat.
Here's an example:
const styleEmployeeBox = css`border-right: black solid 1px;`
const bstyleCard = 'card';
...
return <div css={styleEmployeeBox} className={bstyleCard}>
Inspecting the element shows me:
<div class="card css-nrnevs-ComponentName">
Writing this kind of css-in-js is something I'm choosing to go with because I see a lot of developers in my team writing inlined style on the style prop -- probably because they are lazy and don't want to add a css-classes for small specific fixes.
If they are reasons to why I shouldn't go ahead with this solution, I'd be HAPPY to hear about it. :)
I have directive which does an action when I scroll.
#HostListener('window:scroll')
doSomething() {
console.log(this.el);
console.log(this.el.nativeElement);
if (window.pageYOffset > 10) {
console.log('mouse scroll blahblah');
this.renderer.setStyle(this.el.nativeElement, 'height', '45px');
} else {
this.renderer.removeStyle(this.el.nativeElement, 'height');
}
}
But I want to also add background color to this element AND other styling to element which is in another component. How to do it?
Is it possible to add something like
this.renderer.setStyle([
element1 [ 'height', '45px], ['background-color', 'red']
],
[
element1, 'background-color', 'blue'
]
Or maybe I should use something totally different from 'setStyle'?
I know that my example is messed up but I think there may be something alike, I mean... We are not suppouse to write
this.renderer.setStyle(this.el.nativeElement, 'height', '45px');
this.renderer.setStyle(this.el.nativeElement, 'background-color', 'red');
etc?
Or maybe I shouldn't even try to do it and simply add a class as it's only proper way to add multiple styles?
But how? document.getElementsByClassName('element2') add some class
Got it
Actually I'm not sure that there is one good way to do it. Even in bigger project I can't avoid mixing setting and removing single style with classes. So I wouldn't stick to only one of them. I would definitely not use only setStyle as Kevin suggested as it is terrible to remove later. Yeah, it lets you to adjust everything independently but you can do it simpler, most of the time you won't even need control specific style of element, if - then use class, remove it, if you need to remove single part do it by setStyle/removeStyle.
If you have small project then you can use whatever you want. If it's big... Well, most probably it won't be clean at some point anyway, so mix what works for you :P
const sth = document.getElementsByClassName('myElement');
if (window.pageYOffset > 10) {
this.renderer.addClass(sth[0], 'onScroll'); //for some reason there is array created, so you have to get there by adding [0]
this.renderer.addClass(this.el.nativeElement, 'onScroll');
} else {
this.renderer.removeClass(this.el.nativeElement, 'onScroll');
this.renderer.removeClass(sth[0], 'onScroll');
}
Using addClass() and removeClass() is certainly the cleanest wax to do it, as you can simply tweak your result by adjusting the CSS later.
See Angular Renderer documentation for how to use it.