Importing React State into CSS file - css

I am currently working on a NextJS based project, in which I need to use React State to determine the width of a div. Currently this is being calculated inside a .tsx file using consts and incremented when a button is clicked. The resultant width is then passed down as a prop to the component.
Right now, I'm using inline styling to set the width of the div, but I wanted to know if it was possible to pass the prop directly into the .module.css file I'm using as it will be tidier and much easier for media queries later on. Is that possible?
Also, is it possible to import variables from the media queries back into the .tsx file?
Main file:
const [width, setWidth] = React.useState(0)
const increment: number = maxWidthTop / totalRounds
export default function Labeller() {
function clicked() {
setWidth(width + increment)
}
return (
<Progress
width={width} />
)}
Component file:
import styles from '../../styles/Progress.module.css'
type ProgressProps = {
width: number;
}
export default function ProgressBar(props: ProgressProps) {
return (
<div className={styles.main}>
<div className={styles.bar}>
<div className={styles.base}></div>
<div style={{width: `${props.width}em`}} className={styles.top}></div>
</div>
</div>
)
}

You can't modify the css module dynamically at runtime as the css files are downloaded and parsed by the browser separately from the js. Supplying your width value using inline is styles a good way to go but you are right that it doesn't make media queries easy.
One alternative option would be to write a function that formats a css string with your width variable, and renders the output inside a style element:
import "./styles.css";
const divWidthStyles = (width: number) => `
.${styles.top} {
width: ${width}px;
}
#media only screen and (min-width: 600px) {
.${styles.top} {
width: ${2 * width}px;
}
}
`;
...
export default function ProgressBar(props: ProgressProps) {
return (
<div className={styles.main}>
/* Use style element */
<style>{divWidthStyles(props.width)}</style>
<div className={styles.bar}>
<div className={styles.base}></div>
<div className={styles.top}></div>
</div>
</div>
);
}
Another option would be to set a css variable using javascript, whenever width changes, by making use of useEffect(). Like so:
function ProgressBar(props: ProgressProps) {
const { width } = props;
const divRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (divRef.current) {
divRef.current.style.setProperty("--width", `${width}px`);
}
}, [width]);
return (
...
<div
className={"dynamic_width"}
ref={(ref) => (divRef.current = ref)}
></div>
...
);
}
And then making use of the variable in your css.module file like this:
.dynamic_width {
height: 200px;
background: red;
width: var(--width);
}
#media only screen and (min-width: 600px) {
.dynamic_width {
width: calc(2 * var(--width));
}
}
Side note; there's a library called styled-components that allows you to supply values to the styles of components easily. I find it helps to keep things very tidy and could be a more elegant solution to your problem.

Related

In React,How to make a page that contains two dynamic size components

If data in container A collapses(minimised), Component B should increase vertically in size and appear on full page. Similarly if Component B is collapsed,component A should increase.By default,both the components have equal screen space.
there are tons of ways to do this, you can check how flexbox in CSS works. it should not bee very react specific, All you need to do from react is to know which component is collapsed and which is to expanded.
In the parent component, you'll want to track which component is maximised. Then, pass a maximised prop to component A and component B, and let them set their CSS classes based on it. You could hide most of the content if you just want a mini version of the component.
Assuming you're using function components with hooks, it would look somewhat like this:
const Container = () => {
// Either "A", "B" or null (equal sizes)
const [componentMaximised, setComponentMaximised] = useState(null);
return (
<div className="container">
<A maximised={componentMaximised === "A"}/>
<B maximised={componentMaximised === "B"}/>
</div>
);
};
const A = props => {
return (
<div className={props.maximised ? "component component-maximised" : "component"}>
// ...
</div>
);
};
const B = props => {
return (
<div className={props.maximised ? "component component-maximised" : "component"}>
// ...
</div>
);
};
You'll also want to pass the setComponentMaximised function to each component as a prop if you want them to be able to have a button to maximise and minimise themselves.
For your CSS, use display: flex in combination with flex-grow to set how the items share the space:
.container {
height: 100vh; /* approx full height */
display: flex;
flex-flow: column nowrap;
}
.component {
flex-grow: 1;
overflow: hidden; /* prevent contents from spilling out of component */
}
.component-maximised {
flex-grow: 3;
}
Quick demo of this technique (try changing the classes manually in HTML)
https://codepen.io/gh102003/pen/MWKOQqE
You can use flex-grow: 0 if you just want the component to take up the space it needs.

React: cannot read document.body.style.marginRight

I am having trouble reading the margin-right css property of the body. It seems to fail on initial render, but a few times it seemed to work when I was manually setting the margin-right. I expect it has something to do when when the component is rendering. Tried in useEffect and useLayoutEffect without success.
Pertinent CSS:
body {
margin-right: 10px;
}
Simple create-react-app:
function App() {
const [marginRight, setmarginRight] = useState(
document.body.style.marginRight
);
return (
<div className="App">
<p>BODY Right margin is: {marginRight}</p>
</div>
);
}
HTMLelement.style only returns inline styles. To access style from your css file, you should use:
window.getComputedStyle(document.body).marginRight

Responsive Props in Vue Component

I have a prop called src in a Vue Component that binds to a :style like this:
<template>
<section :class="color" class="hero" :style="{ backgroundImage: src && 'url(' + src + ')' }">
<slot></slot>
</section>
</template>
<script>
export default {
props: ['src', 'color']
}
</script>
What I would like to do is to create a list of responsive props that get used depending on the device or screen size of the site visitor.
For instance, I imagine a list of props like src-sm, src-md, src-lg, etc. The user would enter different image urls for different device sizes and the style attr would use the appropriate url depending on the screen/size.
Is this possible in VueJS. If so, any idea how?
Thanks.
Unfortuently what you are trying to do is not trivial. This is because inline style tags can not accept media queries.
The spec declares:
The value of the style attribute must match the syntax of the contents of a CSS declaration block
Solution 1:
This solution is the simplest, perhaps not entirely what you are looking for.
It works by including img elements, and showing an hiding them via CSS.
<template>
<div>
<img class="image--sm" :src="src.sm" />
<img class="image--md" :src="src.md" />
<img class="image--lg" :src="src.lg" />
</div>
</template>
<script>
export default {
props: {
src: Object
}
}
</script>
<style>
.image--md,
.image--lg {
display: none;
}
#media (min-width: 400px) {
.image--sm {
display: none;
}
.image--md {
display: block;
}
}
#media (min-width: 600px) {
.image--md {
display: none;
}
.image--lg {
display: block;
}
}
</style>
Example: https://jsfiddle.net/h3c5og08/1/
Solution 2:
Image tags may not be the desired effect you are trying to achieve. This solution creates a style tag in the head and injecting the css content to change the background images.
You can not have style tags in Vue template. It will throw an error like:
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as , as they will not be parsed.
As the error describes vue is designed the map state the UI. Using style tags in the template is prohibited because you can cause leaks to the outer world.
Although you can not declaratively styles in a template, we can use a bit of JS in the mounted hook of the component to add targetted and dynamic styles.
First we will need to constrain dynamic styles to this element. We can use the internal id of the created component this._uid, attaching to scope the css. (Note this is internal API so can be subject to change)
<template>
<div class="image" :data-style-scope="_uid">
</div>
</template>
The next part is to generate the style in a computed property, to later inject into a style block. You can expand on this computed property, to conditionaly assign properties ect. Note: keep the properties to the dynamic values only.
css () {
const selector = `.image[data-style-scope="${this._uid}"]`
const img = val => `${selector} { background-image: url("${val}"); }`
const sm = img(this.sm)
const md = img(this.md)
const lg = img(this.lg)
return `
${sm}
#media (min-width: 200px) { ${md} }
#media (min-width: 300px) { ${lg} }
`
}
This generated string from the css computed property is what we will now use when creating the style tag at mount. At mount we create a style node and append to the head. Assigning the nodes to the vm for references.
Using the references in the vm we can watch changes to the computed updating the style node.
Remember to clean up before destorying the component, removing the style node.
{
data () {
return {
// Reference data properties
style: null,
styleRef: null
}
},
mounted () {
// Create style node
let style = document.createElement('style')
style.type = "text/css"
style.appendChild(document.createTextNode(''))
// Assign references on vm
this.styleRef = style
this.style = style.childNodes[0]
// Assign css the the style node
this.style.textContent = this.css
// Append to the head
document.head.appendChild(style)
},
beforeDestroy () {
// Remove the style node from the head
this.style.parentElement.removeChild(this.style)
},
computed: {
css () {
// ...
}
},
watch: {
css (value) {
// On css value change update style content
this.style.textContent = this.css
}
}
}
Working Example: https://jsfiddle.net/bLkc51Lz/4/
You could also try the module described here: https://alligator.io/vuejs/vue-responsive-components/ which is called vue-responsive-components
It lets the component change its CSS depending on its own width (not on the entire browser's width)

React - responsive app - better to hide element in component via CSS or not render it

I'm currently working on a React.js-based app. Lets say we have a Header component in React with a small logo component inside it that should only be displayed at mobile resolution levels. I'm passing an isMobile prop from the parent component. This prop is based on:
const mql = global.matchMedia(`(min-width: 768px)`);
mql.addListener(() => this._mediaQueryChanged());
this.setState({
mql: mql,
isMobile: !mql.matches
});
_mediaQueryChanged() {
this.setState({
isMobile: !this.state.mql.matches
});
}
<Header isMobile={this.state.isMobile} />
And in Header:
render() {
const {isMobile} = this.props;
const containerClass = classNames('header-component', {
'is-mobile': isMobile
});
return (
<header className={containerClass}>
{
isMobile &&
(
<section className="mobile-header">
<Button className="toggle-menu" onClick={() => this._toggleMenu()}>
<Icon name="menu" />
</Button>
<Logo className="header-logo" />
</section>
)
}
<span>login</span>
</header>
);
}
and if mql matches then it is not mobile. My question is: should I pass this param and re-render the component every time we change Media queries? During re-render hide/show logo component? Or just use CSS to show/hide it and the component will be mounted there all the time. No re-renders tho.
Thoughts?
I agree this may be a job for just CSS.
.header-class .logo {
display:none;
}
#media only screen and (min-width: 768px) {
.header-class .logo {
display:block;
}
}
I think it depends on what you're trying to achieve. If you have universal rendering with critical path CSS extraction and trying to shave milliseconds go for the react-only solution. Otherwise css-only solution will do but equally as well as the react-only solution because you're probably re-rendering already. That sneaky isMobile has just triggered a render somewhere else in your codebase (or will in the future); plus, you've just lost the extensibility of that react offers.

How to use SyntaxHighlighter for inline code

I am using Syntax Highlighter for my website.But how can I use it for inline code? I mean when I use it for inline code it still shows the line number 1 and I want to remove it as it makes no sense to show line number for inline code
i.e
I want
"This is a java print code System.out.println("Hello");"
instead of
"This is a java print code 1 |System.out.println("Hello");"
(notice the line number in second case)
I searched it on google but no success.
There is a configuration for turning line number on/off:
setting gutter attribute allows you to turn gutter with line numbers on and off.
Here is reference , Here is DEMO
You will have to use a custom code block and style it yourself to achieve it:
Algo:
If Inline Display Custom Block
Else Allow SyntaxHighlighter to do its magic.
I have used the below code:
CodeHighlighter.js
import {Prism as SyntaxHighlighter} from "react-syntax-highlighter";
import {dracula} from "react-syntax-highlighter/dist/esm/styles/prism";
import './CodeHighlighter.css'
import {Card} from "#mui/material";
function cleanChildren(children){
for (var i = 0; i < children.length; i++){
children[i] = children[i].trim()
}
return children
}
export default function CodeHighlight({node, inline, className, children, ...props}) {
var lang = "";
try {
lang = className.replace("language-", "");
} catch {
lang = "";
}
return (
<div className='highlightRoot'>
{inline ?
(
<Card className='inlineCode' variant='outlined'>
<code >{children}</code>
</Card>
)
: (
<div>
<SyntaxHighlighter
language={lang}
style={dracula}
showLineNumbers={!inline}
startingLineNumber={1}
children={cleanChildren(children)}
/>
<p>{lang}</p>
</div>
)
}
</div>
);
}
CodeHighlighter.css
.highlightRoot{
display: inline;
}
.inlineCode{
display: inline;
padding: 0 4px;
background-color: #4b4b59;
color: white;
}
I have been using this in conjunction with ReactMarkdown library.
What I was able to achieve:

Resources