React jss overrides parent styles and retains child style - reactjs

in using the jss lib in react with a custom theme that merges two themes together for use. Previously there was no problems but to type it correctly and create an ease of development, it was modified a bit of how the createUseStyles works. Here is an example of the usage before the change:
import { cn, createUseStyles, useTheme } from "#styling";
const useStyles = createUseStyles({
container: {
width: "500px",
height: "100px",
}
});
const Child = ({extraStyles}:{extraStyles:string[]}) => {
const theme = useTheme();
const styles = useStyles({theme});
return (
<div className={cn(styles.container, ...extraStyles)}>
</div>
)
}
// parent
import { cn, createUseStyles, useTheme } from "#styling";
import Child from "#child";
const useStyles = createUseStyles({
large: {
width: "999px",
height: "999px",
}
});
const Parent = () => {
const theme = useTheme();
const styles = useStyles({theme});
return (
<Child extraStyles={[styles.large]}/>
)
}
where cn just combined the classes together. This would result in a stylesheet where the parents styles would override the childs, allowing for flexibility.
After some necessary modification the createUseStyles is typed and generated within the styling. It looks something like this:
//styling
import {
createUseStyles as createStyles,
useTheme,
ThemeProvider,
Styles,
} from "react-jss";
const createUseStyles = (styles) => {
return (props) => {
const tempTheme = useTheme();
const temp = createStyles(styles);
return temp({ ...props, theme: tempTheme });
};
};
export { ThemeProvider, createUseStyles };
I have excluded the extra typing, but it was essentially to save the need to typescript createUseStyles and the usage of useTheme every single time within a component.
...But in doing so & in usage the exact same example as provided above with this new createUseStyle, the child injects it's style above the parents and doesn't pull in the props styling. Using !important on every single prop style is extremely tedious.
I'm confused why just pulling useTheme outside would break the injection structure (assuming that is the cause of this problem)?

Possible, you can add index for createUseStyles to set order of injection, something like this:
// Child
const useStyles = createUseStyles({
container: {
width: "500px",
height: "100px",
}
}, { index: 2 });
// Parent
const useStyles = createUseStyles({
large: {
width: "999px",
height: "999px",
}
}, { index: 1 });
I don't know how it works, if you need to add into every componente style or in entire stylesheet. The official documentation of React JSS isn't clear but it has in JSS API.

Related

Next.js: How to get applied styles from element?

Let's say I have global.css
.test {
background-color: black;
width: 50px;
height: 50px;
}
For some reason, I need to get the styles data from applied element. I tried refs but it always return empty string.
import { useState, useEffect, useRef } from "react";
const IndexPage = () => {
const divEl = useRef<HTMLDivElement | null>(null);
const [divStyle, setDivStyle] = useState({} as CSSStyleDeclaration);
useEffect(() => {
if (divEl.current) {
setDivStyle(divEl.current.style);
}
}, [divEl.current]);
return (
<div>
<div ref={divEl} className="test"></div>
<pre>{JSON.stringify(divStyle, undefined, 2)}</pre>
</div>
);
};
export default IndexPage;
Is it because next.js SSR or should I add something to dependency array?
code sandbox here
You can use computed styles to get what you need, although it won't be a "simple" object with properties. You'll need to query each property individually:
if (divEl.current) {
setDivStyle(getComputedStyle(divEl.current));
}
and then you can do something like:
divStyle.getPropertyValue("background-color")
Here's an example sandbox (forked from yours) that demostrates it.

Cannot create a toggle switch with React JSS (react-jss) [duplicate]

I have seen in a lot of the MUI code that they use pseudo selectors in their react styled components. I thought I would try to do it myself and I cannot get it to work. I'm not sure what I am doing wrong or if this is even possible.
I am trying to make some CSS that will offset this element against the fixed header.
import React from 'react';
import { createStyles, WithStyles, withStyles, Typography } from '#material-ui/core';
import { TypographyProps } from '#material-ui/core/Typography';
import GithubSlugger from 'github-slugger';
import Link from './link';
const styles = () =>
createStyles({
h: {
'&::before': {
content: 'some content',
display: 'block',
height: 60,
marginTop: -60
}
}
});
interface Props extends WithStyles<typeof styles>, TypographyProps {
children: string;
}
const AutolinkHeader = ({ classes, children, variant }: Props) => {
// I have to call new slugger here otherwise on a re-render it will append a 1
const slug = new GithubSlugger().slug(children);
return (
<Link to={`#${slug}`}>
<Typography classes={{ root: classes.h }} id={slug} variant={variant} children={children} />
</Link>
);
};
export default withStyles(styles)(AutolinkHeader);
I found out that the content attribute needed to be double quoted like this
const styles = () =>
createStyles({
h: {
'&::before': {
content: '"some content"',
display: 'block',
height: 60,
marginTop: -60
}
}
});
and then everything worked like expected
As #Eran Goldin said, check the value of your content property and make sure it's set to a string "". Odds are, you are doing something like this:
'&::before': {
content: '',
...
}
Which doesn't set the content property at all in the output stylesheet
.makeStyles-content-154:before {
content: ;
...
}
In Material-UI style object, the content of the string is the css value, including the double quote, to fix it simply write
'&::before': {
content: '""', // "''" will also work.
...
}
Without having to explicitly set a height
The above solutions require explicit height. If you want height to be dynamic, you can do something like this:
&::after {
content: '_'; // Any character can go here
visibility: hidden;
}

material ui withStyles and WithTheme - How to access theme

const MySlider = withStyles({
root: {
color: theme => theme.palette.primary.main
}
}, {withTheme: true})(Slider);
I did expect the withTheme option would make the theme accessible inside withStyles. If so then how? because theme is not defined and its also not found in props ?
Any idea
withStyles function can accept a callback instead of an object giving you the access to the theme object like this
const NewButton = withStyles((theme) => {
root: {
color: theme.palette.primary
}
}, { withTheme: true })(Button);

Material UI: how do I use Theme values with withStyles?

I am building a component with Material UI. I am using the default theme file (as per here https://material-ui.com/customization/default-theme/).
I know I can bring in certain values from the theme with makeStyles as such:
import { makeStyles, Theme } from '#material-ui/styles';
const useStyles = makeStyles((theme: Theme) => ({
something: {
color: theme.palette.common.black,
},
}));
That works fine.
But how do I use those same values with styled Material UI components? eg:
import withStyles from '#material-ui/core/styles';
const StyledBadge = withStyles({
badge: {
color: theme.palette.common.black,
},
})(Badge);
I tried replicating the above, eg:
const StyledBadge = withStyles((theme: Theme) => ({
but this doesn't work.
Would anyone know the correct way of doing this?
I tried creating Component withStyles as below and it is working perfectly.
import { withStyles } from "#material-ui/core";
export const ExpansionPanelDetails = withStyles(theme => ({
root: {
padding: theme.spacing(1),
},
}))(MuiExpansionPanelDetails);

CSS empty definition necessary to have the type as specificity selector (material UI in React)

The styling for the unselected toggle button works nicely.
But the style of the selected toggle button does not appear when you don't define an empty class selector:
./App.js
import * as React from "react";
import { render } from "react-dom";
import { createStyles, WithStyles } from "#material-ui/core";
import { ToggleButtonGroup, ToggleButton } from "#material-ui/lab";
import { withStyles } from "#material-ui/core/styles";
const styles = () =>
createStyles({
root: {
backgroundColor: "blue",
"&$selected": {
color: "blue",
backgroundColor: "yellow"
}
},
// this empty definition needs to be here otherwise it won't work
selected: {}
});
export interface IAppProps extends WithStyles<typeof styles> {}
const App: React.FC<IAppProps> = ({ classes }: IAppProps) => {
return (
<ToggleButton
classes={{
root: classes.root,
selected: classes.selected
}}
selected={true}
>
option 1
</ToggleButton>
);
};
const AppWithStyles = withStyles(styles)(App);
const rootElement = document.getElementById("root");
render(<AppWithStyles />, rootElement);
This perhaps has something to do with the type definition in the props. When you remove the selector 'selected' from the styles definition, it is not available anymore in the interface IAppProps.
How can you define this type in the interface?
Working example: https://codesandbox.io/s/elated-sammet-y7wh7?fontsize=14
update 1: Also tried Augmenting the props as described in the material-ui docs:
const styles = () =>
createStyles({
toggleButton: {
backgroundColor: "blue",
"&$toggleButtonSelected": {
color: "blue",
backgroundColor: "yellow"
}
},
});
export interface IAppProps {
classes: {
toggleButton: string;
toggleButtonSelected: string;
};
}
const App: React.FC<IAppProps> = ({ classes }: IAppProps) => {
// ...
With no luck.
update 2: using a hook makes the type casting redundant, but it also won't fix this:
import * as React from "react";
import { render } from "react-dom";
import { createStyles, makeStyles } from "#material-ui/core";
import { ToggleButtonGroup, ToggleButton } from "#material-ui/lab";
const useStyles = makeStyles(() =>
createStyles({
root: {
backgroundColor: "blue",
"&$selected": {
color: "blue",
backgroundColor: "red"
}
},
// this still needs to be there...
// selected: {}
})
)
export interface IAppProps {}
const App: React.FC<IAppProps> = () => {
const classes = useStyles();
return (
// ...
)
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
I believe you just have a misunderstanding of how JSS works and the meaning of some of the syntax. The relevant documentation is here.
When you define your styles object (or function taking in the theme and returning an object), each key in that object is referred to by JSS as a "rule". The key is the rule name and JSS will translate the value into a CSS class. The classes object that you get back from useStyles or that gets injected as a prop when using withStyles then maps the rule names to the generated CSS class names.
The $ruleName syntax is a way to refer to the CSS class name of one of the other rules in your styles object. The & refers to the parent rule. In your example you have rules called root and selected (when it isn't commented out).
The following:
root: {
backgroundColor: "blue",
"&$selected": {
color: "blue",
backgroundColor: "red"
}
},
selected: {}
would compile to CSS like the following:
.root-0 {
background-color: blue;
}
.root-0.selected-0 {
color: blue;
background-color: red;
}
By passing the following to Material-UI:
classes={{
root: classes.root,
selected: classes.selected
}}
selected={true}
You are telling it to apply "root-0 selected-0" as class names in addition to the class names applied for the default styling. Without the empty selected: {} rule name, you can't refer to $selected from the root rule (JSS should be giving you a warning in the console if you do).
There is a slightly simpler alternative (as of v4) for referring to the selected class name. selected is one of the Material-UI special states that it refers to as pseudo-classes and the documentation provides the default class name for each (e.g. Mui-selected).
This means you can do the following:
root: {
backgroundColor: "blue",
"&.Mui-selected": {
color: "blue",
backgroundColor: "red"
}
}
This is no longer referencing another rule, so selected: {} isn't needed and neither is selected: classes.selected needed in the classes prop. Instead this is referencing the actual class name that Material-UI applies for the default styling when selected={true}.

Resources