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

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.

Related

How to add conditional styles in emotion/react?

I'm using react and emotion. I need to change the style of an element according to a boolean value. The below code does not work. How can I combine multiple styles correctly?
import { css } from "#emotion/react"
const navSticky = css({
transform: "translateY(-10px)",
})
const navStyle = css({
background: "red",
})
...
<nav css={isSticky ? {...navStyle, ...navSticky} : navStyle}> </nav>
As first variant i suggest use styled component for this. Set background as base property and set transform if isSticky prop has been passed with true value.
As second variant i suggest correct your example. Use JSX Pragma with jsx function from '#emotion/react'. This allow use css prop. Docs: https://emotion.sh/docs/css-prop#jsx-pragma
// first variant
const Nav = styled.nav`
background: red;
transform: ${p => p.isSticky && "translateY(-10px)"};
`
const App = () => <Nav isSticky={true}>Some Elements</Nav>
// second variant
/** #jsx jsx */
import {jsx} from '#emotion/react'
const navSticky = {
transform: 'translateY(-10px)',
}
const navStyle = {
background: 'red',
}
const App = () => (
<nav css={{...navStyle, ...(isSticky && navSticky)}}>Some Elements</nav>
)

Custom components in react-markdown

I would like to create a custom component in react-markdown. Specifically I need to be able to create a tag that will allow me to create a tooltip. E.g. [name]{ tooltip content}. I found a similar solution in the link. Unfortunately the component is created as a regular div and not as my component. I don't know exactly what I did wrong.
my code:
import React from "react";
import styled from "styled-components";
import ReactMarkdown from "react-markdown";
import directive from "remark-directive";
import { visit } from "unist-util-visit";
import { h } from "hastscript/html.js";
const Test = styled.span`
font-size: 12rem;
font-family: "DM Serif Display", serif;
`;
const TestComp = ({ ...props }) => {
return <Test> {props.children}</Test>;
};
const components = {
myTag: TestComp,
};
// remark plugin to add a custom tag to the AST
function htmlDirectives() {
return transform;
function transform(tree) {
visit(
tree,
["textDirective", "leafDirective", "containerDirective"],
ondirective
);
}
function ondirective(node) {
var data = node.data || (node.data = {});
var hast = h(node.name, node.attributes);
data.hName = hast.tagname;
data.hProperties = hast.properties;
}
}
var markdown =
' Some markdown with a :myTag[custom directive]{title="My custom tag"}';
const Text = () => {
return (
<ReactMarkdown
className={"markdown"}
components={components}
remarkPlugins={[directive, htmlDirectives]}
children={markdown}
linkTarget={"_blank"}
/>
);
};
export default Text;
I use markdown-to-jsx to use custom component.
https://www.npmjs.com/package/markdown-to-jsx

ReactJS and Less, set dynamic class

I have a component like this:
import '../MLExtraIcon.less';
const MLExtraIcon = ({ text, color }) => (
<span className={`ml-extra-icon ml-extra-icon-color-${color}`}>
{text}
</span>
);
So, I call <MLExtraIcon> components with 2 props: text and color
But currenly, I have hardcoded the "color" as a class in a less file which is:
// MLExtraIcon.less
#import "../colors";
.ml-extra-icon {
&.ml-extra-icon-color-passion {
background-color: #passion;
}
}
And in the imported colors file I have a bunch of variables like:
// colors.less
#opportunities: #F47521;
#innovation: #1976BD;
#passion: #CB198A;
// etc...
How could I select dynamically the variable to use in the .less file? I mean, my problem is here:
.ml-extra-icon {
&.ml-extra-icon-color-passion {. <--- that's hardcoded, I would like to know if
background-color: #passion;
}
}
...if there's a way to give the variable name as a parameter, for instance:
.ml-extra-icon {
&.ml-extra-icon-color-$color {
background-color: $color;
}
}
(I'm inventing that syntax, in order to explain what I want to achieve). I don't want to create different css clases for each already declared less variable (like the hardcoded example)
Any tip?
If you want to do it dynamic you can use styled-components. Example codesandbox
import React from "react";
import styled from "styled-components";
export default function App() {
const ColorWrapper = styled.span`
color: ${(props) => props.color};
`;
const MLExtraIcon = ({ text, color }) => (
<ColorWrapper color={color}>{text}</ColorWrapper>
);
return (
<div className="App">
{MLExtraIcon({ text: "Your passion", color: "blue" })}
</div>
);
}
Also you can create global theme file instead of local const theme, to use it throughout the project.

React jss overrides parent styles and retains child style

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.

React + Jest: How to test private styled components?

I have a Charities component that shows text "Sorry..." when the status prop is === "error":
import React from "react";
import styled from "styled-components";
const Notification = styled.p`
text-align: center;
padding: 10px;
font-family: Raleway;
display: ${props => (props.hide ? "none" : "block")};
`;
const ErrorNotification = styled(Notification)`
background: #e3c7c4;
`;
export const Charities = ({
..., status
}) => (
<Container>
<ErrorNotification hide={status !== "error"}>
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
...
</Container>
);
export default Charities;
I'm trying to test this with jest like this:
import React from "react";
import { mount, shallow } from "enzyme";
import { Charities } from "./index";
describe("Charities", () => {
let props;
let mountedCharities;
const charities = () => {
if (!mountedCharities) {
mountedCharities = mount(<Charities {...props} />);
}
return mountedCharities;
};
beforeEach(() => {
props = {
status: undefined,
...
};
mountedCharities = undefined;
});
describe("when status is pending", () => {
beforeEach(() => {
props.status = "pending";
});
it("doesn't render error", () => {
expect(charities().text()).not.toMatch(/Sorry/); // <---------- FAILS
});
});
});
My test fails with:
Expected value not to match:
/Sorry/
Received:
"Sorry, something was wrong with your payment. Please try again.Congratulations! You have successfully made a donation."
It seems like it's loading the children of the styled components even when it doesn't meet the conditions. How would I test this?
Your code is working as expected, it's just that styled() works by putting class names on the elements to control the styling.
In the unit test ErrorNotification is still there but it has css classes on it that will give it display: none in the final rendered HTML.
To make your components easier to unit test I recommend doing the hiding within Charities like this:
import React from "react";
import styled from "styled-components";
const Notification = styled.p`
text-align: center;
padding: 10px;
font-family: Raleway;
display: block;
`;
const ErrorNotification = styled(Notification)`
background: #e3c7c4;
`;
export const Charities = ({
..., status
}) => (
<Container>
{status !== "error" ? null : (
<ErrorNotification>
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
)}
...
</Container>
);
export default Charities;
That way if the status in the props for Charities is anything except 'error' then ErrorNotification won't be rendered at all.
You are using the attribute hide which maps to 'display: none' when true this still renders the component albeit invisibly you should do something like this instead:
{ status === "error" &&
<ErrorNotification >
Sorry, something was wrong with your payment. Please try again.
</ErrorNotification>
}

Resources