ReactJS advanced custom styling - css

Using reactjs only, is it possible to do advanced styling similar to
#primary-nav .list-group-item.active {}
// or
#secondary-nav .list-group-item:hover>.background-item {}
in the first example I could do some rather simple javascript logic to figure out if the component is "active" but on the second example it's just so much simpler with css.
Is there a clear react+js solution for these situations that comes close to the simplicity of css?

className is applied exactly like a regular HTML class. So to correctly target .background-image like in
.list-group-item:hover>.background-item
Your jsx structure should look like
import './index.css'
const Component = () =>{
return(
<div className='list-group-item'>
<div className='background-item' />
<span>
<div className='background-item' /> /*Targeting nested items '>' */
</span>
</div>
)
}
You can use jss and clsx to have dynamic and conditional styles. Here is an example using MUI styles(hooks API), but you can use styled components, react-jss or implement you're own style's solution based on jss.
import { makeStyles } from '#material-ui/styles'
import clsx from 'clsx'
const styles = {
root:{
color: 'white',
'&:active':{
color: 'red'
}
},
hidden:{
opacity: 0
}
}
const useStyles = makeStyles(styles)
const Component = ({ open }) =>{
const classes = useStyles()
const rootStyle = clsx({
[classes.root] : true,
[classes.hidden] : !open
})
return <div classsName={rootStyle} />
}
jss also have lots of cool features like theming support, styles interpolation (a personal favorite), nested selectors, style's rules,etc. Definitely worth taking a look.
Notice that clsx doesn't require jss to work, it's just a helper to conditionally apply classes. You can use it like clsx({'foo' : true, 'bar': false})

Related

How do you style your React.js components differently depending on where you are using them in your application?

Let's say you have a navbar and when you're using this component on your homepage you want it to have a certain background color and display property, but when you use that same navbar component on another page in your application you want to change these CSS properties. Seeing as the component has one CSS file linked how would you change the style of a component depending on where it is being rendered?
My personal favourite method nowadays is styled components. Your component might look something like this:
// NavBar.js
import styled from 'styled-components'
const StyledDiv = styled.div`
width: 100%;
height: 2rem;
background-color: ${props => props.bgColor};
`
const NavBar = (bgColor) => {
return <StyledDiv bgColor={bgColor}>
}
Then to use it in your different contexts you simply pass the color prop:
// homepage.js
<NavBar bgColor="red" />
// otherpage.js
<NavBar bgColor="#123ABC" />
Styled components are becoming a very popular way of doing things, but be aware that there are a huge array of ways you can do this.
https://styled-components.com/
(Code not tested)
Well If you just want to use plain CSS then you can change the className based on route so the styles also changes.
Example:
import { useLocation } from "react-router-dom";
const Navigation = () => {
let location = useLocation();
...
return(
<nav className={location.pathname === "/home" ? "homepage-navbar" : "default-navbar"}>
...
</nav>
)
}
You can write longer condition for multiple pages as well.
Other better thing you can do is pass the location.pathname and value of className as prop.
import { useLocation } from "react-router-dom";
const Home = () => {
let location = useLocation();
...
return (
<>...
<Navigation location={location.pathname} styleClass={"homepage-navbar"}/>
</>
)
}
const Navigation = ({location, styleClass}) => {
...
return(
<nav className={location === "/home" ? styleClass : "default-navbar"}>
...
</nav>
)
}
So now you can pass different values for className from different components and get different styles for the navbar.

MUI v5 - Seperate styling from component file

I want to separate styling from component File in MUI v5. The way I did it in v4 was using makeStyles like that:
Page.style.js:
import { makeStyles } from "#material-ui/core";
export const useStyles = makeStyles({
root: {
background: "white",
},
});
Page.js:
import React from "react";
import useStyles from "./Page.style";
const Page = () => {
const classes = useStyles();
return (
<div className={classes.root}></div>
)
}
makeStyles is now legacy and I heard it will be deprecated next version.
What is the proper way to separate styling and component into different files in v5?
The recommended styling APIs in v5 are styled()/sx prop. If you use styled, you can separate the styling code by creating a reusable styled component. The sx prop is more suitable for inline one-off style so it's not the best choice to use in this scenario:
const Div = styled('div')({
background: "white",
});
export Div;
import React from "react";
import { Div } from "./Div";
const Page = () => {
return (
<Div />
)
}
Besides that, you can also use variant in MUI v5. The way it works is you create a custom styles and assign a name for it called variant so instead of specifying a className like before, you set the variant prop like this:
<Button variant="myCustomVariant">
Button
</Button>
Custom variant can be created using createTheme. See this answer for more detail. Be aware that for now, this feature is not supported in all components:
const theme = createTheme({
components: {
MuiButton: {
variants: [
{
props: { variant: 'myCustomVariant' },
style: {
textTransform: 'none',
border: `2px dashed grey${blue[500]}`,
},
},
],
},
},
});
I would recommend CSS Modules or Plain CSS because the other styling solution might get deprecated in the near future, one good example is the makeStyle which is now considered a Legacy styling solution.
You can find more information in Mui's webpage under the Style library interoperability section.
It is suggested to use either sx or styled.
Document: https://mui.com/system/basics/

Material UI 5 class name styles

I migrated from Mui 4 to 5 and wonder how to use class names. If I want to apply certain styles to just one component there is the SX property. However, I'm struggling with using the same class for multiple components. In v4 my code looked like this:
export const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
padding: theme.spacing(1),
margin: 'auto',
},
})
)
I could import this useStyles hook in any component and use it like this:
const classes = useStyles()
...
<div className={classes.root}>...</div>
This docs say, that I can 'override styles with class names', but they don't tell how to do it:
https://mui.com/customization/how-to-customize/#overriding-styles-with-class-names
Do I have to put these styles in an external CSS file?
.Button {
color: black;
}
I would rather define the styles in my ts file.
I also found this migration guide:
https://next.material-ui.com/guides/migration-v4/#migrate-makestyles-to-emotion
I don't like approach one, because using this Root wrapper, it is inconvenient to apply a class conditionally. (Especially for typescript there is some overhead) Approach two comes with an external dependency and some boilerplate code.
Ideally I would use styles like this, perhaps with one rapper function around the styles object:
export const root = {
padding: theme.spacing(1),
margin: 'auto',
}
<div className={root}>...</div>
Of course, the last approach doesn't work, because className wants a string as input. Does anybody know an alternative with little boilerplate code?
I suggest you take a look at emotion's documentations for details. The sx prop is actually passed to emotion.
You can do something like this:
const sx = {
"& .MuiDrawer-paper": {
width: drawerWidth
}
};
<Drawer sx={sx}/>
Equivalent to MUI v4
const useStyles = makeStyles({
drawerPaper: {
width: drawerWidth,
}
});
const classes = useStyles();
<Drawer
classes={{
paper: classes.drawerPaper,
}}
/>
Answering your exact question, there are use cases (I think yours is not one of them and you should use styled components) however for those like me who stumble upon it and want a "exact answer to this question" and not a "do this instead", this is how you achieve to retrieve the class names.
This is so far undocumented.
For functional components, using emotion, here an use case where you have a 3rd party component that expects, not one, but many class names, or where the className property is not where you are meant to pass the property.
import { css, Theme, useTheme } from "#mui/material/styles";
import { css as emotionCss } from "#emotion/css";
const myStyles = {
basicClass: {
marginLeft: "1rem",
marginRight: "1rem",
paddingLeft: "1rem",
paddingRight: "1rem",
},
optionClass: (theme: Theme) => ({
[theme.breakpoints.down(theme.breakpoints.values.md)]: {
display: "none",
}
})
}
function MyComponent() {
cons theme = useTheme();
// first we need to convert to something emotion can understand
const basicClass = css(myStyles.basicClass);
const optionClass = css(myStyles.optionClass(theme));
// now we can pass to emotion
const basicClassName = emotionCss(basicClass.styles);
const optionClassName = emotionCss(optionClass.styles);
return (
<ThirdPartyComponent basicClassName={basicClassName} optionClassName={optionClassName} />
)
}
When you have a Class Component, you need to use the also undocumented withTheme from #mui/material/styles and wrap your class, if you use the theme.
WHEN IT IS NOT AN USE CASE
When your component uses a single className property just use styled components.
import { styled } from "#mui/material/styles";
const ThrirdPartyStyled = styled(ThirdPartyComponent)(({theme}) => ({
color: theme.palette.success.contrastText
}))
Even if you have dynamic styles
import { styled } from "#mui/material/styles";
interface IThrirdPartyStyledExtraProps {
fullWidth?: boolean;
}
const ThrirdPartyStyled = styled(ThirdPartyComponent, {
shouldForwardProp: (prop) => prop !== "fullWidth"
})<IThrirdPartyStyledExtraProps>(({theme, fullWidth}) => ({
color: theme.palette.success.contrastText,
width: fullWidth ? "100%" : "auto",
}))
Even if each one has some form of custom color, you just would use "sx" on your new ThrirdPartyStyled.
When you are just trying to reuse a style around (your use case)
const myReusableStyle = {
color: "red",
}
// better
const MyStyledDiv = styled("div")(myReusableStyle);
// questionable
const MySpanWithoutStyles = styled("span")();
// better
const MyDrawerStyled = styled(Drawer)(myReusableStyle);
function MyComponent() {
return (
<MyStyledDiv>
questionable usage because it is less clean:
<MySpanWithoutStyles sx={myReusableStyle}>hello</MySpanWithoutStyles>
<MySpanWithoutStyles sx={myReusableStyle}>world</MySpanWithoutStyles>
these two are equivalent:
<MyDrawerStyled />
<Drawer sx={myReusableStyle} />
</MyStyledDiv>
)
}
Now what is "presumably" cool about this is that your style, is just an object now, and you can just import it and use it everywhere without makeStyles or withStyles, supposedly an advantage, even when to be honest, I have never used that of exporting/importing around; the code seems a bit cleaner nevertheless.
You seem to want to use it so all you do is.
export const myStyles {
// your styles here
}
because this object is equivalent in memory, and it is always the same object, something that is easier to mess up with styles, it should be as effective or even more than your hook, theoretically (if it re-renders often even when setup may be longer), which stores the same function in memory but returns a new object every time.
Now you can use those myStyles everywhere you deem reasonable, either with styled components or by assigning to sx.
You can further optimize, say if it's always a div that you use that is styled the same way, then the styled component MyStyledDiv should be faster, because it is the same and done each time. How much faster is this? According to some sources 55% faster, to me, it is taking 4 weeks of refactor and the JSS compatibility with emotion is bad, all mixed with SSR is making everything unusable and slow and broken, so let's see until then when the whole is refactored.
Here is a pattern that I've found useful in MUI 5. It allows you to keep style definitions in the same file but isolated, & avoids repeated function calls for every CSS property where you need to access your theme (e.g. width: ({ spacing }) => spacing(12))). It also feels similar to MUI's native CSS API.
Create a function that takes your theme as an argument & returns an object of named style groups. Then reference those groups directly in your sx props. This also allows for the use of classNames in a way similar to Material-UI 4.
import { useTheme } from '#mui/material';
import clsx from 'clsx';
export const NavItem = (props) => {
// Bring in style groups
const sx = styles(useTheme());
// Define classNames
const classNames = clsx({
isActive: props.isActive
});
return (
{/* Use classNames and style groups */}
<ListItemButton className={classNames} sx={sx.button}>
<ListItemAvatar sx={sx.avatar}>{props.icon}</ListItemAvatar>
<ListItemText>{props.label}</ListItemText>
</ListItemButton>
);
}
// Define style groups
function styles(theme) => {
return {
button: {
paddingX: 6,
'&.isActive': {
backgroundColor: theme.palette.secondary.light
}
},
avatar: {
'.isActive &': {
border: '2px solid green'
}
}
};
}
I'm in the same boat, about six months behind, i.e., starting to make the transition to v5 from v4 now... Just when I thought I had a handle on it all!
Having read this post and trying a few things out, I was able to replicate the ability to re-use a chunk of css. I'm a big fan of what used to be the overrides prop; that feature hasn't gone away, it's just under a different prop (loosely speaking). Regardless, I mention it because it provides access to what I like a lot about css: selectors.
To hit all MUI-Drawers my pref is for whatever the new overrides is. For targeted reuse of css I like the following:
import { reuseThisCss } from 'sharedCss';
export default styled(Drawer)(({ theme, ownerState }) => {
...
return {
'& .MuiDrawer-paper': {
boxShadow: xxl,
border: 'none',
'& .MuiListItemText-root': reuseThisCss,
},
};
export default ThisSpecificDrawerVariant;
Note: The focus is not on using styled (It's not my goto approach).
The css in the return value is the equivalent to the following css: .MuiDrawer-paper .MuiListItemText-root {...}.
This says, "select all .MuiListItemText-root under the .MuiDrawer-paper parent. If I want to optimize the render, while increasing the dependency on a specific hierarchy, I'll specify/expand on the selector that much more with whatever lies between the .MuiDrawer-paper and MuiListItemText-root. For instance, in my case:
...
return {
'& .MuiDrawer-paper': {
boxShadow: xxl,
border: 'none',
'& > a > li > div > .MuiListItemText-root': reuseThisCss,
},
};
Finally, per a question in the comments, generally this will not prevent a nested application of the style. In my experience, marking each level with a className is useful. I only "mark" the element that signals the start of a new level. So, if it were Drawer in the above example, I would start the css selector with .MUI-Drawer.level-3. The rest of css remains the same.
I still have not figured out if whether setting the className dynamically remains a performant and sufficiently flexible goto... TBD.
If you are using makeStyles or withStyles to provide CSS class, you can follow the instruction below.
CSS overrides created by makeStyles

How can I dynamically set style of a mapped element without using inline styling?

I'm coding a React function with parent component containing an array of objects:
let const ingredients = [
{name:"lettuce",color:"green"},
{name:"tomato",color:"red"}
]
...
In a child component, there is a map function that breaks down an array to single items to be displayed in a div.
What is the best practice for defining CSS styling for an object className:"name" to set backgroundColor: {ingredient.color};? I'm trying to avoid manual entry of the entire set of key/values of 'ingredients', to allow updating the object without breaking the code.
I'm currently using inline styling, which I have been advised against. Currently using:
let burg = props.toppings.map((item) => {
const divColor = {backgroundColor: item.color};
return (<div style={divColor}>{item.name}</div>)
Inline style is bad when you have other solution to do what you want. Here, you have a string that is the color (red, green, etc.) so you could write a css class for every color, but that is of course a really bad idea. Inline style is the good way to do it here.
I would suggest setting the class of the div instead of the style. That way you can change the look without resorting to inlining the style.
You could create a css class for lettuce with the background color green, instead of using the item.color you'd set class={ item.name }
You can use this way.
css can be more handy if you use scss
// css
.color-green {
color: green;
}
.color-red {
color: red;
}
import React from "react";
import "./styles.css";
const ingredients = [
{ name: "lettuce", color: "green" },
{ name: "tomato", color: "red" }
];
const Vegetable = ({ color, text }) => {
return (
<p>
this is <span className={`color-${color}`}>{text}</span> color{" "}
</p>
);
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{ingredients.map((item, index) => {
return <Vegetable key={index} color={item.color} text={item.name} />;
})}
</div>
);
}

Using existing css class in Stripe Elements for React

I'm trying to use our existing CSS classes in my Stripe Elements form. According to this document https://stripe.com/docs/stripe-js/reference under section Elements options, I can pass my existing CSS classes but my code below didn't work.
const customStripeClasses = {
base: 'app-form-default input',
};
class MyPaymentForm extends Component {
render() {
return(
<div>
<CardElement classes={customStripeClasses} />
</div>
);
}
}
Basically, I'm trying to get Stripe Elements form components to use the CSS classes we created for regular HTML elements e.g. input, textarea, etc.
Any idea how I can get this to work?
As far as I can see from the docs you need to use react-stripe-elements to use stripe with react. And CardElement has no prop classes. You can pass css classes to it with className:
import React from 'react';
import {CardElement} from 'react-stripe-elements';
class CardSection extends React.Component {
render() {
return (
<div>
<CardElement className="app-form-default input" />
</div>
);
}
};

Resources