React & CSS Modules: how to style components without assuming their content - css

This challenge is best illustrated with a little example:
import React from 'react'
import styles from './styles.pcss'
const Spinner = () => (
<article className={styles.spinner}>
{/* <div> elements here*/}
</article>
)
export default Spinner
I'd like to change the position of the spinner component so this has to be done by the parent of the spinner. I see 3 solutions:
Use a CSS rule targeting the <article> tag (the root element of the spinner). I don't like this as the parent component is making assumptions about the internal structure of the spinner. If this tag changes, then all styles targeting it will break. I must be able to change the internal structure of my components without worrying about breaking many areas of my app.
Pass a className down to the spinner component. It's much better as no assumption is made regarding the internals of the spinner. But as potentially every component could be positioned by their parent (a very common CSS task), I would have then to implement the className being passed down (incl. prop types validation, etc) for every component that would need custom styling. There must be a better solution.
import React, { PropTypes } from 'react'
import classNames from 'classnames'
import styles from './styles.pcss'
const Spinner = ({ className }) => (
<article className={classNames(styles.spinner, className)}>
{/* <div> elements here*/}
</article>
)
Spinner.propTypes = {
className: PropTypes.string
}
export default Spinner
Use a containing <div> around the component I want to style:
<div className="spinner"><Spinner /></div>
But this leads to bloated markup with unnecessary <div>s in situations where many elements have to be styled (like setting their positions).
What are your recommendations?
Thx

I'm leaning towards solution 3. This way, the styled component (spinner) stays clean and doesn't have to implement additional logic for getting a className, validating it and merging it with its own classes.
I wanted to see how 3rd party libraries implemented this scenario, and they favor solution 3 as well. Here's the related discussion about styling <FormattedMessage> of react-intl.
Furthermore, the wrapping <div> would only be used when styling the container itself (typically, the position), not the contents of the component. And if many components have to be positionned, it's very likely that they are subcomponents, ex: <TodoItemList> and in this case the <TodoList> parent component would pass down the className to them as they won't exist outside this particular context.

Related

Issue with :global() css-module selectors not being pure in NextJS

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.

Combing global class names with component specific style

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. :)

Why does CSS styling disappear in React when directly changing route in browser URI?

I have a simple front-end React app created using npx create-react-app. The app is using react-router-dom routes. When I directly change the URI in the browser from say, localhost:3000/ to localhost:3000/search it will navigate to the <Route>but with no CSS rendered; just HTML from the component.
How can I make sure CSS is rendered in the new route when directly navigating in the browser? My future goal is to be able to copy and paste a route in a new tab and navigate to the correct page and display results from an API.
Css style sheets will need to be imported either at the root level or within the file itself.
style sheets need to be imported when used and then the corresponding classname will need to be used within the component or tag.
Another useful way to set react css without style sheets is by using in line styles
e.g
<div style ={{float: "right", textAlign : "center"}}> </div>
EDIT
A really easy way to get styles going within a react project is install bootstrap.
then buttons and stuff can be assigned classNames such as
<div className = "jumbotron"></>
this will leave a grey box around the items.
<div className = "btn btn-primary"></>
this will give you a blue styled button.
Any more information or help within your application let me know and provide some code snippets.
You can use styled-components. styled-components are widely used in ReactJs and React Native and it's a perfect choice.
styled-component : https://www.npmjs.com/package/styled-components
I realized that react apps created using npx create-react-app allow you to import a css module for components.
Given the component, Button.jsx, you can simply create a css module with the convention, [module-name].module.css. For the case of Button.jsx, create a file named Button.module.css, import "styles" from the module. Styles will be an object containing all the CSS styles.
I if I had a folder named "components" with all my components, I could make a folder within "components", say called "compStyles", and create all the [module-name].module.css files in there.
Button.module.css:
/* class names must be camelCased */
.myButton {
margin: 0 auto;
}
span {
fontSize: 20px;
}
If I had the above mentioned file structure, I could import and use like so:
import React from 'react';
import styles from './styles/Button.module.css';
const Button = () => {
return (
<div className={styles.myButton}>
<button><span>Some Button</span></button>
</div>
)
}
export default Button;
Styles for the span will be automatically applied, and any other class will be referenced by styles.className. Create one file for every component, and each component's CSS will take care of itself and not break like it would if it was in the public folder.

Wrapper element for svelte storybook

I am currently writing a component in svelte and in order to work on only this component I am also using storybook.
The problem is that because of the css library I am using the component will be incorrectly rendered unless it is properly wrapped by a parent element. In short, this component is list element and without the list wrapper the css will be funky.
So the question is, can I somehow tell storybook to wrap my component in a div?
i.e. something like this
storiesOf("Kanban card", module)
.add(
"small",
() => ({
Component: Card,
template: "<div class='wrapper'><Card /></div>",
props: {
...
}
})
);
Your best bet would be to create a separate Svelte component, specifically for that story. This approach also gives you a way to use slots properly, something that's not clearly available through Storybook.
Something like kanban-card.story.svelte containing:
<script>
import Card from '../wherever/kanban-card.svelte';
// export the props that the component needs
// pass up all events you want to handle
</script>
<Card on:eventYouWant />

How to dynamically set complex CSS of an Angular 2+ component?

I'd like to ask for a little nudge to get my brain out of the box I got it into.
Context
Angular 4 using Angular CLI and AoT.
All methods mentioned in https://angular.io/docs/ts/latest/guide/component-styles.html describe ways to set complex CSS of a component while it is being written by a developer.
After a component is created, Angular allows to adjust individual styles and even assign various CSS class names to tags in the component as you please, all that using [ngClass], <div [class.something]="condition">, various #HostBinding decorators and some other methods.
However, none of the above can change the CSS declaration the component is using. The methods above can either (a) use what is already available in the stylesheet defined by the developer or (b) set individual CSS properties to individual HTML tags in the component's template.
Question
How would I update the CSS for the whole component on runtime so that all elements in that component respond to the new CSS?
Say I introduce a new style for a div.someClass and I want all matching elements div.someClass to reflect the new style.
Plunker
A showcase of my attempts is here: https://plnkr.co/edit/N2C40cSb7hd1AyOxWWdT
The button should be red, based on the value of MyChildComponent.styles
I think I understand why it doesn't work the way I would expect: shortly said, styles are built in the component during compilation, not runtime, including those found inside <style /> tags in the template.
But knowing why it doesn't work doesn't help me to make it work.
Any help highly appreciated.
Solution 1
Inserting a new css class is not possible ( as far as i know ) but you can insert css properties to your component dynamically.
I modified your dynamicStyles() to this
get dynamicStyles(): any{
return {'background': 'red' };
}
that returns an object instead of string because you will pass this object to ngStyle of your button.
In your template, I change the button like this
<button type="button"
[ngStyle]="styles">
Button
</button>
Here's a plunkr
Solution 2
This is something that I would not recommend but in your case it might be useful. You can add this
encapsulation: ViewEncapsulation.None
and the import
import {ViewEncapsulation} from '#angular/core'
to your #Component.You can leak your component's css so that you can use it on your child component. Then in your child component, add a [ngClass] in your button so that you can just pass a variable via #Input() if it should be red.
<button type="button"
[ngClass]="{'redButton': isButtonRed}"
>Button</button>
And in your style.css
.redButton{
background:red;
}
And in your main component.
<div>
<h2>Hello name</h2>
<my-child [isButtonRed]="true"></my-child>
</div>
Here's another plunkr
Hope this helps.

Resources