ReactJS - Responsive Layout using Media Queries - javascript

I am working on a web app, and I am trying to make it responsive, but I am running into a few problems. I am using media queries to detect if the screen width is > than a certain amount or < than a certain amount. Based on that, I would want to change the layout of my component, so that it does not cram and move everything out of alignment. However, it seems like if my goal is to reorder the child components within my component using conditional rendering based on the media query result, then my code would be repeated multiple times. Hence, I am checking to see if there are any other ways to accomplish this.
Below is a screenshot of the browser when the screen size gets smaller, the contents get too cramped up, and hence it messes up the alignment.
Below is a snippet of my code (this code represents the right side of the image -> Revenue and Revenue KPI)
<Row justify="space-around">
<Col span={11}>
<Statistic
title="Revenue"
value={data[metric].revenue}
valueStyle={{ color: '#3f8600' }}
/>
</Col>
<Divider type="vertical" />
<Col span={11}>
Revenue KPI
<Button shape='circle' size='small' onClick={() => handleClick('rev', metric, 'post')} style={{ marginLeft: '5%' }}>
<PlusOutlined />
</Button>
<Statistic
value={data[metric].rev_kpi}
valueStyle={{ color: '#3f8600' }}
/>
</Col>
</Row>
What I would want to do, is to change the grid layout once the screen width is smaller than a certain amount, and instead of the components above being in the same Row, I would want each to be in their Row (stacking on top of each other). However, if I were to do this using conditional rendering, it seems like I would have to repeat the code quite a fair bit, which seems tedious and messy. Hence, I am hoping if there are any more efficient methods to achieve what I would want to make.
The UI package I am using is AntD (where I got the components for Row, Col, Statistic, Button)
https://ant.design/components/grid/
this is my media query function (I use this function to return a boolean based on the query I pass in)
import { useEffect, useState } from "react";
function useMediaQuery(
query,
defaultMatches = window.matchMedia(query).matches
) {
const [matches, setMatches] = useState(defaultMatches);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) setMatches(media.matches);
const listener = () => setMatches(media.matches);
media.addListener(listener);
return () => media.removeListener(listener);
}, [query, matches]);
return matches;
}
export default useMediaQuery;
All help is appreciated, thanks all! Do guide me along as I am new to React and especially new to implementing responsive websites!

Since Row and Col in ant design seems to be based on flex you should try to use the CSS order property to re-arrange your elements
https://developer.mozilla.org/en-US/docs/Web/CSS/order
It is also available as a prop on the Col component:
https://ant.design/components/grid/#Col
CSS order demo (use media query to change order value for various screen sizes):
div {
display: flex;
flex-direction: column;
}
#reverse:checked ~ div>p:first-child {
order: 2;
}
<input type="checkbox" id="reverse"/>
<label for="reverse">reverse</label>
<div>
<p>First text element in HTML</p>
<p>Second text element in HTML</p>
<div>
For even more complex rearrangement of your elements you could use CSS Grid:
grid-template-areas: give custom names to areas in your grid
grid-area: place element in the grid using one of your custom names
use media-queries to change grid-template-areas and it should work nicely
https://css-tricks.com/snippets/css/complete-guide-grid/

Related

When should I use style instead of sx prop in Material-UI?

The style and sx prop in MUI components pretty much do the same thing. The sx prop offers a few shorthand syntaxes, and allows you to access the theme object. But apart from that, they seem identical. When are you supposed to use one over the other?
To really understand which one to use, we need to understand what's happening under the hood. Material UI uses emotion(, or whatever styling engine you chose manually), in order to style its components. On the surface, the following two might seem to be doing the same thing:
<Box sx={{ height:'50px', width:'25px' }}/>
<div style={{ height:'50px', width:'25px' }}/>
Both render divs with the required height and width, to the DOM. But for the div, the styles are applied as inline styles, whereas the Box applies the styles in the form of a class, to the div element. The class definition itself, is stored in the head tag, which you can inspect, to see the following
This is all well and fine, as long as we're declaring the styles only once. But stuff really goes crazy when you add dynamic styling. Perhaps there is a state variable controlling the height of your div.
function CustomComponent(){
const [computedHeight,setComputedHeight]=useState();
useEffect(()=>{
window.addEventListener('resize',()=>setComputedHeight(window.innerWidth/2))
},[]);
return (
<Box sx={{ height:computedHeight, width:'25px'}}/>
<div style={{ height:computedHeight, width:'25px'}}/>
)
This is a common scenario, where some external variable(the width of the browser window for eg.) determines some property of your component. What happens is every time this state changes into a new value, MUI creates a new class, sets it as the class for the Box, and adds the definition to the <head> tag of your website, as a brand new <style> tag. So in very little time, the head tag fills up with hundreds of style tags, which is clearly undesirable.
However, in the case of your div tag, the styles are located inline. So no matter if the value changes once, or a hundred times, there is only one definition of the style, and it exists on the element itself. No need to worry about it.
EDIT 1:
MUI creates a new style tag only for a style that hasn't been used before. To illustrate, if your sx prop dynamically changes the color between 'red' and 'blue' like this,
sx={{
color: dynamicValue ? 'red' : 'blue',
}}
MUI will only create two tags(for the two possible values of color), no matter how many times you change the value of dynamicValue. MUI will just use the old style tags.
Note on Pseudo selectors:
Another thing to note is that inline styles cannot make use of pseudo elements(like ::after, or ::before), or pseudo classes(like :hover, :focus etc.), as inline styles directly affect the current element. You would have to employ a workaround like css variables in order to change the styles on pseudo elements/classes.
TLDR; Put your dynamic styles(the ones that change based on some variable) in the style prop, and put all the static styles in the sx prop
style vs sx in MUI 5
Sandbox Link
sx prop works only on MUI components like Grid, Box and so on, whereas style prop works on both MUI components and HTML like elements (JSX) such as span, article,h1 and so on.
sx prop is very helpful when compared to style prop in some cases as explained below. There might be many differences between them but these are the 3 key differences I have noticed and you might encounter them often.
Defining Media queries
Nesting styles
Making use of theme parameter (to get palette) inside sx object which we can't do in style like this - color: (theme) => theme.palette.primary.main
1. Defining Media queries
One difference between style and sx which is most popular is, defining media queries based on the Material UI theme.
How would you deal with media queries in style prop? You can use your own breakpoints and do it the way you do in CSS for sure, but you cannot use Material UI breakpoints.
This is where sx prop comes in handy where you can define media queries and other MUI provided properties to alter your styles.
Example
import { Typography, createTheme, ThemeProvider } from '#mui/material'
let theme = createTheme()
function Demo() {
const stylePropCSS = {
backgroundColor: 'lightblue',
/* theme prop is not understood here,
so this breakpoint will not work,
and text will not turn into orange */
[theme.breakpoints.down('xl')]: {
backgroundColor: 'orange',
},
}
/* theme prop is understood here.
breakpoints can be applied here based on MUI theme
*/
const sxPropCSS = {
backgroundColor: 'lightgreen',
[theme.breakpoints.up('xs')]: {
backgroundColor: 'orange',
},
}
return (
<ThemeProvider theme={theme}>
{/* style prop */}
<Typography style={stylePropCSS}>
This text will <b>NOT TURN</b> orange as
I am using style prop and MUI 'theme' is not understood
</Typography>
<br />
{/* sx prop */}
<Typography sx={sxPropCSS}>
This text will <b>TURN</b> into orange as
I am using sx prop and MUI 'theme' is understood
</Typography>
</ThemeProvider>
)
}
export default Demo
2. Nesting styles and using theme inside sx prop
You can nest the styles when using sx prop, but can't do this when using style prop.
With sx prop
<Box sx={styles.post}>
<Typography variant="h4">This is the title</Typography>
</Box>
Box is a div of background black, and I need h4 to be yellow OR MUI primary color. With this requirement, I can nest my styles when I use sx prop like this where I place h4 inside post and define sx only on Box
const styles = {
post: {
backgroundColor: 'black',
// nesting h4 inside post
h4: {
// color:'yellow' // OR
color: (theme) => theme.palette.primary.main,
/* I cannot use theme inside style object. Since I am going
to apply styles.post to sx prop,I could make use of theme
object here as an argument */
},
},
}
With style prop
<Box style={styles.post}>
<Typography variant="h4" style={style.heading}>
This is the title
</Typography>
</Box>
const styles = {
post: {
backgroundColor: 'black',
},
// I can't nest the heading inside post, so defining it outside post
heading: {
color: 'yellow',
// color: (theme) => theme.palette.primary.main, // THIS WON'T WORK with style prop
},
}

How to add a space, break or div at specific height dynamically?

So basically I want to print out a page as a pdf from a react app, that include multiple components. number of components change from 4 up to 12 and the height of the components also change based on content from the store.
I need every 900px(size of my a4 page) to add a space so that none of the components get split between pages. or to split the hole page in to even 900px parts based on how many components are rendered.
This is not a question can be answered with code snippet. I will explain how i would tackle with this problem.
Steps right after the the print event (on button click or automated...)
I would get each components current height (as you mentioned 4 up to 12 components)
const element = document.getElementById('element');
element.offsetHeight // height
I would create placeholder component (div), which would be used to fill the gap vertically between components to be printed.
const Placeholder = ({height}) => (
<div style={{height: `${height}px` }}> </div>
)
I would start looping all the components until they can fit into 900px. When the chunk reaches the 900px i would fill the gap with placeholder component. That would force rest of the components to move to next page.
E.g. Let's say:
Component 1 is 300px
Component 2 is 300px
Component 3 is 700px
Component 4 is 300px
loop {
// first page
<Component1 />
<Component2 />
<Placeholder height={300} />
// second page
<Component3 />
<Placeholder height={200} />
<Component4 />
<Placeholder height={600} />
}

Virtualized table scroll on window like a normal table

I am currently trying to integrated a virtualized table in react using react–virtualized, however the table does not seem to render correctly and I am trying to understand as to why. I also have a few other problems that I cant find documentation on how to do as I have pointed out below.
It would be great if anyone has something or could help.
Below is the code I am using and my goal with this is to be able to:
1- Scroll with the page the table
2- Height of rows be automatic and widths
3- The numbers shown in the row need to be formatted some depending on condition and same for wealth depending on condition may show red or green using css.
Now I have been able to get scroll to partially work however at the top of the scroll it shows massive white space when I scroll down while the table is still in view.
<WindowScroller>
{({ height, isScrolling, onChildScroll, scrollTop }) => (
<Table
width={1000}
autoHeight
height={height}
headerHeight={30}
rowHeight={40}
scrollTop={scrollTop}
isScrolling={isScrolling}
onScroll={onChildScroll}
rowCount={this.setData.length}
rowGetter={({index}) => this.setData[index]}>
<Column label="Country" dataKey="country" />
<Column label="Flag" dataKey="flag" />
<Column label="Population" dataKey="population" />
<Column label="Wealth" dataKey="wealth" />
</Table>
)}
</WindowScroller>
Also just out of curiosity is there any library or way that you can just render content on scrolling eg infinite loading on scrolling down the screen that way it renders everything out of the screen as it comes in or as its about 200px from coming into view ?
You can use Material-UI tables for a customizable table, it's very easy and flexible.
If you prefer to use a specific table please share code.

How to modify the z-index of Material-UI <Select> dropdown div?

I am rendering an <AppBar> with a large z-index value (using withStyles, it has a value of theme.zIndex.modal + 2 which computes to 1202).
The reason for this is to ensure my <Drawer> component remains hidden behind the <AppBar> when it's opened (i.e. a clipped drawer).
However when I render a <Select> component within my appbar, the 'dropdown' div does not have a large enough z-index value to be displayed, and so it ends up being hidden behind the appbar.
A basic example is as follows:
let Test = ({classes}) => (
<AppBar className={classes.appbar} elevation={2} position='relative'>
<Toolbar>
<Select>
<MenuItem>{"Item 1"}</MenuItem>
<MenuItem>{"Item 2"}</MenuItem>
</Select>
</Toolbar>
</AppBar>
)
const styles = theme => ({
appbar: {
zIndex: theme.zIndex.modal + 2,
margin: 0
}
})
Test = withStyles(styles)(Test);
Overriding the z-index on any of <Select>'s exposed classes does not seem to fix my problem. How can I ensure <Select> appears in front of <AppBar>?
You can use the style attribute on all components in Material-UI
<Select style={{zIndex: X}}>
...
</Select>
Cf: https://v0.material-ui.com/#/components/select-field
For details about all the available styles attributes.
You can use PaperProps on Drawer component to adjust zindex instead of changing Select component's zindex.

How to add a custom image/icon in ANTD Design menu?

Using this example: https://ant.design/components/layout/#components-layout-demo-side
How can I add a custom image or icon instead of the default icons.
I tried:
<Menu.Item to="/" key="2">
<img className="ant-menu-item" src={require('image.png')} />
<span>Shopify</span>
<Link to="/shopify">Home</Link>
</Menu.Item>
But that does not look good or does not respect the collapsed behaviour
I tried several different ways of creating custom icons, and the one that was easiest and worked best was to use the component property of the antd Icon component. Just give it a functional component that returns whatever image you want to use:
<Icon component={() => (<img src="/image.svg" />)} />
This seems to work well within menu items and submenus, except that the icons don't line up perfectly with the menu text like the built-in icons do. I ended up adding transform: translateY(-3px) to the CSS to compensate for this (might depend on the image you use).
On the other hand, the official solution (for SVG images only) is to use the #svgr/webpack plugin to turn the SVG file into a component. This may have some advantages as far as layout and coloring (antd icons seem to prefer actual <svg> elements over <img> elements with SVG image files). But I'm not sure because I didn't go to the trouble of setting it up.
<Menu.Item to="/" key="2">
<img className="ant-menu-item" src=="{{ "image.png" | asset_url }}"/>
<span>Shopify</span>
<Link to="/shopify">Home</Link>
</Menu.Item>
I hope this might be work.
you need handle separate css file and put it this code
.ant-menu-item{background-image: url("theme5.jpg");}
icon:<img src="/static/icons/BH_tainan.svg" height={20} style={{margin:"0 12px 0 0" ,paddingTop:10 ,float:"left"}}/>,
need float:"left" in your style
A bit of an old question, but I thought I'd post another solution here for those not wanting to go the webpack route like the docs recommend. You can simply create your own SVG component and then pass it to the antd icon component.
Something like this
// Icons.tsx
export const MyIcon = () => {
return (
<svg>
// svg path data
</svg>
);
};
Then
// app.tsx
import Icon from "#ant-design/icons";
import { MyIcon } from "./Icons";
const App = () => {
return (
<Icon component={MyIcon} />
)
}
I fiddled with this a little bit in a sandbox, and it seems to work just fine. I was using antd 4.23.3 for reference. Didn't test it a huge amount so there might be some style adjusting needed, not sure.

Categories

Resources