Material UI date picker behaving differently on mobile device - reactjs

I'm using Material UI datepicker in a React project. I've created a calendar + time selection (code below). For some reason when I test this on Chrome desktop using the device toolbar to emulate iphone 8 it works fine, but when I go to the site on my actual iphone 8, the UI is broken (images below). Any thoughts?
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Button, Typography } from '#material-ui/core';
import { makeStyles } from '#material-ui/core/styles';
import moment from 'moment';
import { MuiPickersUtilsProvider, DatePicker } from '#material-ui/pickers';
import DateFnsUtils from '#date-io/date-fns';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexDirection: 'row',
padding: theme.spacing(3),
[theme.breakpoints.down('sm')]: {
padding: 0,
},
},
picker: {
display: 'flex',
flexDirection: 'row',
[theme.breakpoints.down('sm')]: {
flexDirection: 'column',
},
},
timeContainer: {
display: 'flex',
flexDirection: 'column',
marginLeft: theme.spacing(2),
overflowY: 'scroll',
[theme.breakpoints.down('sm')]: {
alignItems: 'center',
marginLeft: 0,
marginBottom: theme.spacing(3),
},
},
timeButton: { width: 226, height: 50 },
}));
export default function Timesgrid(props) {
const classes = useStyles();
const [selectedDate, setSeletedDate] = useState(moment());
const disableDay = date => {
const formattedDate = moment(date).format('YYYY-MM-DD');
return formattedDate in props.availability &&
props.availability[formattedDate].length > 0
? false
: true;
};
const handleDateChange = date => {
setSeletedDate(moment(date));
};
const renderTimes = () => {
const dateStr = moment(selectedDate).format('YYYY-MM-DD');
const availability = props.availability[dateStr];
return availability && availability.length ? (
<div className={classes.timeContainer}>
{availability.map((time, i) => (
<Button
style={{ marginTop: i === 0 ? 0 : 10 }}
key={time}
className={classes.timeButton}
variant="outlined"
color="primary"
onClick={() => props.onDatetimeSelected(moment(time))}
>
<b>{moment(time).format('LT')}</b>
</Button>
))}
</div>
) : (
<Typography variant="h6" className={classes.timeContainer}>
No availability
</Typography>
);
};
const renderPicker = () => {
return (
<div className={classes.picker}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<DatePicker
disablePast
shouldDisableDate={disableDay}
variant="static"
defaultValue={selectedDate}
value={selectedDate}
onChange={handleDateChange}
/>
</MuiPickersUtilsProvider>
{renderTimes()}
</div>
);
};
return <div className={classes.root}>{renderPicker()}</div>;
}
Timesgrid.propTypes = {
availability: PropTypes.object.isRequired,
onDatetimeSelected: PropTypes.func.isRequired,
};

The answer to your question as we discussed in comments was to add the following CSS styling.
overflow: scroll
height: 100%
Justification:
The reason I suggested this solution was due to the comparisons in your images. The popup screen for the component was mushed together on the native browser popup but, not on the Chrome DevTools popup. This indicated that the native browser container was not allowing for overflow to occur.
Setting the height of the component to 100% ensures that it takes up the full height of it's container and allowing overflow ensures that if your component is greater than 100% of the containers height that it will render as shown in the devtools view.
Furthermore, In your reply you said your solution was
overflowY: scroll,
height: 100%
and overflow: scroll allows for overflow on both axes. Since the x-axis doesn't require overflow, this is a better solution.
Glad to see it worked.

Related

How can I make a progress bar like this in React with Material UI?

I want to make a progress bar in image with Material UI in React like this:
I tried with a customProgress bar, something like:
export const CustomLinearProgress = styled(LinearProgress)(({ theme }) => ({
height: 35,
borderRadius: 3,
[`&.${ linearProgressClasses.colorPrimary }`]: {
backgroundColor: '#00b4e559',
},
[`&.${ linearProgressClasses.bar }`]: {
borderRadius: 3,
backgroundColor: "#00B4E5"
},
}));
but it doesn't work.
You can use MUI's CircularProgress component and add a label for the progress percent inside it.
import * as React from "react";
import CircularProgress, {
CircularProgressProps,
} from "#mui/material/CircularProgress";
import Typography from "#mui/material/Typography";
import Box from "#mui/material/Box";
function CircularProgressWithLabel(
props: CircularProgressProps & { value: number }
) {
return (
<Box sx={{ position: "relative", display: "inline-flex" }}>
<CircularProgress
variant="determinate"
size={150}
thickness={5}
{...props}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: "absolute",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Typography
variant="caption"
component="div"
color="text.secondary"
>{`${Math.round(props.value)}%`}</Typography>
</Box>
</Box>
);
}
export default function CircularStatic() {
const [progress, setProgress] = React.useState(10);
React.useEffect(() => {
const timer = setInterval(() => {
setProgress((prevProgress) =>
prevProgress >= 100 ? 0 : prevProgress + 10
);
}, 800);
return () => {
clearInterval(timer);
};
}, []);
return <CircularProgressWithLabel value={progress} />;
}
Change size and thickness props to achieve the style you want.
Learn more ways to control your circular progress in it's API page.

React - MUI styled div causes rerendering and fetching data multiple times on window resizing

I am displaying multiple charts in a Grid. Every chart fetches different data from my server.
When I resize the window the data is fetched again and the chart renders from scratch. I want to avoid the re-fetching.
I use highcharts as chart library, useQuery to fetch data (tried with useEffect hook aswell) and a Grid from material UI.
Update to my original question:
I have wrapped my app.js in a dashboard layout. This layout uses a MUI styled component. I figured when I use a normal div instead, the re-fetching stops. But also the Outlet is not displayed correctly as it's hidden behind the sidebar.
Here are my Components:
dashboard-layout.js
import { useState } from 'react';
import { DashboardNavbar } from './dashboard-navbar';
import { DashboardSidebar } from './dashboard-sidebar';
import { DashboardContent } from './dashboard-content';
import { DrawerContextProvider } from "../../contexts/drawer-context";
export const DashboardLayout = (props) => {
const [isSidebarOpen, setSidebarOpen] = useState(true);
return (
<>
<DrawerContextProvider>
<DashboardContent/>
<DashboardNavbar onSidebarOpen={() => setSidebarOpen(true)} />
<DashboardSidebar
onClose={() => setSidebarOpen(false)}
open={isSidebarOpen}
/>
</DrawerContextProvider>
</>
);
};
DashboardContent.js
import { Box, useMediaQuery } from '#mui/material';
import { useTheme, styled } from '#mui/material/styles';
import { Outlet } from 'react-router-dom';
import { useDrawerContext } from "../../contexts/drawer-context";
import DashboardFooter from './dashboard-footer';
export const DashboardContent = () => {
const { isOpened } = useDrawerContext();
const theme = useTheme();
const isLargeScreen = useMediaQuery(theme.breakpoints.up("md"));
const DashboardContentRoot = styled('div')(({ theme }) => ({
display: 'flex',
flex: '1 1 auto',
maxWidth: '100%',
paddingTop: 64,
paddingLeft: isLargeScreen && isOpened ? 280 : 0
}));
return (
<DashboardContentRoot >
<Box
sx={{
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
width: '100%',
px: isLargeScreen ? 1.5 : 1,
py: isLargeScreen ? 1.5 : 1
}}
>
<Outlet/>
<DashboardFooter />
</Box>
</DashboardContentRoot>
);
};
The problem with re-rendering and therefore re-fetching persists even when I empty DashboardContentRoot like so:
const DashboardContentRoot = styled('div')(({ theme }) => ({
}));
but when I use div instead, no refetching happens:
<div>
<Box
sx={{
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
width: '100%',
px: isLargeScreen ? 1.5 : 1,
py: isLargeScreen ? 1.5 : 1
}}
>
<Outlet/>
<DashboardFooter />
</Box>
</div>
Components for my original question:
Chart.js
import React from "react";
import { useQuery } from "react-query";
import '../HighchartsStyle.css';
async function fetchData(){
const data = await (
await fetch("myserver/data")
).json()
console.log("fetching again")
return data
}
export default function Chart1(){
const {status, data, error, isLoading } = useQuery('data', fetchData);
return (
<div>
hohoho
</div>
);
}
Grid.js
import React from "react";
import { Chart1 } from "../components/charts/Chart1";
export default function ChartOverviewView() {
return (
<div >
<Chart1 />
</div>
);
}
fetching again is displayed every time I resize the window. I want to fetch the data once. Save it temporarily and use the already-fetched data every time I have to rerender the chart again.
What is the best practice to do so?
Thanks!
I am not sure if this suggestion works.
declare a variable with useRef.
then
if(useRefVariable.current !== data[1]) setSeriesData(data[1]);
try changing like this... If the previous state is equal with the new state then don't run setState or you could do smth like this
setSeriesData((prev)= > {if(!lodash.isEqual(prev, data[1])) return data[1]})
you should use lodash library to check if objects are equal. second way might be more professional looking
The re-rendering and therefore re-fetching of data happened because I wrapped my application in a dashboard-layout and for displaying the dashboard content depending on the state of the sidebar (opened or closed) I used a styled div component with MUI.
I don't know why this happened, but when I changed to using a Box instead, re-rendering stopped.
My dashboard content now looks like this:
<Box
sx={{
display: 'flex',
flex: '1 1 auto',
maxWidth: '100%',
paddingTop: '64px',
paddingLeft: isLargeScreen && isOpened ? '280px' : 0
}}
>
<Box
sx={{
display: 'flex',
flex: '1 1 auto',
flexDirection: 'column',
width: '100%',
px: isLargeScreen ? 1.5 : 1,
py: isLargeScreen ? 1.5 : 1
}}
>
<Outlet/>
<DashboardFooter />
</Box>
</Box>

mui v5 what is the best way to use style with pragmatic condition?

I'm upgrading a project from material ui v4 to v5 and struggle to update classes/styles properly.
this is a sandBox :
https://codesandbox.io/s/69629346-mui-v5-theming-with-emotion-mui-forked-2j8vze?file=/demo.tsx:1611-1618
In this code 2 box are displayed with 2 ways of applying style. I want to avoid using makeStyles and use SX/emotion as recommanded.
So backgroundColor is red, on hover it become blue.
It works on both.
Now if i click the switch, the backgroundColor become yellow, but on hover of second box the color stay blue instead of grey.
what i'm missing ? thanks
import React, { useState } from "react";
import { makeStyles } from "#mui/styles";
import clsx from "clsx";
import { Box, Switch } from "#mui/material";
import { createTheme, ThemeProvider } from "#mui/material/styles";
const theme = createTheme();
const useStyles = makeStyles((theme) => ({
imageWithBorder: {
height: theme.spacing(10),
width: theme.spacing(30),
padding: theme.spacing(2),
margin: theme.spacing(2),
backgroundColor: "red",
"&:hover": {
backgroundColor: "blue"
}
},
greyHover: {
backgroundColor: "yellow",
"&:hover": { backgroundColor: "grey" }
}
}));
const styles = {
imageWithBorder: {
height: 80,
width: 240,
padding: 2,
margin: 2,
backgroundColor: "red",
"&:hover": {
backgroundColor: "blue"
}
},
greyHover: {
backgroundColor: "yellow",
"&:hover": { backgroundColor: "grey" }
}
};
export default function Test() {
const classes = useStyles();
const [checked, setChecked] = useState(false);
return (
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Box>
Enable grey hover : <Switch checked={checked} onChange={handleChange} />
</Box>
<p>1 With clsx & useStyles</p>
<Box
className={clsx(classes.imageWithBorder, checked && classes.greyHover)}
/>
<p>2 With sx & plain styles</p>
<Box sx={[styles.imageWithBorder, checked && styles.greyHover]} />
</Box>
);
function handleChange(event) {
setChecked(event.target.checked);
}
}
export default function BasicUsage() {
return (
<ThemeProvider theme={theme}>
<Test />
</ThemeProvider>
);
}
By some reason mui doesn't accept backgroundColor: "grey". It's not even render it in the output css.
See gif
Instead, use gray or hex value.
https://codesandbox.io/s/69629346-mui-v5-theming-with-emotion-mui-forked-d0npw6?file=/demo.tsx

How can I smoothly animate a Material UI LinearProgress over a fixed time period?

I am trying to animate over 60 seconds and here's what I have so far:
export default function TimeLoader({ timeout }: ITimeLoaderProps) {
const [progress, setProgress] = useState(1);
useInterval(() => {
setProgress(p => Math.min(p * 1.1, 100));
}, 100);
console.log({ progress });
return (
<LinearProgress variant="determinate" value={progress} />
);
}
But obviously this isn't done over 60 seconds. It finishes rather quick. If I increase the timeout time from 100 to something else, then it's a bit jerky.
Thanks SO!
The example below uses CSS rather than JS to animate the progress bar over 60 seconds. It accomplishes this by using the indeterminate variant, but then customizing its CSS. The indeterminate variant leverages two bars in its animation. This example suppresses the second bar and changes the first bar to animate over 60 seconds once instead of 2.1 seconds infinitely repeating. This example also changes the keyframes part of the animation so that it ends with a full bar, rather than ending with the bar disappearing. Using forwards in the animation causes the final state to stick when the animation finishes.
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import LinearProgress from "#material-ui/core/LinearProgress";
const useStyles = makeStyles({
root: {
width: "100%"
},
bar1Indeterminate: {
width: "auto",
animation: "$indeterminate1 60s linear forwards"
},
bar2Indeterminate: {
display: "none"
},
"#keyframes indeterminate1": {
"0%": {
left: "-35%",
right: "100%"
},
"100%": {
left: "0%",
right: "0%"
}
}
});
export default function LinearDeterminate() {
const classes = useStyles();
return (
<div className={classes.root}>
<LinearProgress
classes={{
bar1Indeterminate: classes.bar1Indeterminate,
bar2Indeterminate: classes.bar2Indeterminate
}}
variant="indeterminate"
/>
</div>
);
}
Relevant documentation:
https://developer.mozilla.org/en-US/docs/Web/CSS/animation-fill-mode
https://developer.mozilla.org/en-US/docs/Web/CSS/#keyframes
Here's an equivalent example using v5 of Material-UI leveraging styled and Emotion's keyframes:
import React from "react";
import { styled } from "#material-ui/core/styles";
import LinearProgress from "#material-ui/core/LinearProgress";
import { keyframes } from "#emotion/react";
const indeterminate1Keyframes = keyframes({
"0%": {
left: "-35%",
right: "100%"
},
"100%": {
left: "0%",
right: "0%"
}
});
const StyledLinearProgress = styled(LinearProgress)({
"& .MuiLinearProgress-bar1Indeterminate": {
width: "auto",
animation: `${indeterminate1Keyframes} 60s linear forwards`
},
"& .MuiLinearProgress-bar2Indeterminate": {
display: "none"
}
});
export default function LinearDeterminate() {
return (
<div style={{ width: "100%" }}>
<StyledLinearProgress variant="indeterminate" />
</div>
);
}

How to 'justify-content: center' for MUI Pagination?

I am trying to center the contents of the Pagination. However, this does not work. On console, I need to justify the ul wrapper and I can not find any information on MUI site related to the pagination props or a guide on how to center the item.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Pagination from '#material-ui/lab/Pagination';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
marginTop: theme.spacing(2),
},
},
pagination: {
alignItems: 'center',
justify: 'center',
}
}));
const Paginated = (props) => {
const classes = useStyles();
return (
<div className={classes.root}>
<Pagination className={classes.pagination} count={props.totalPage} color='primary' />
</div>
);
};
export default Paginated;
and I have been trying on codesandbox as well. https://codesandbox.io/s/material-demo-zv1ps?file=/demo.js
Is there any way I can do this without having an additional box or Grid wrapper to wrap it out?
root: {
"& > *": {
marginTop: theme.spacing(2),
justifyContent:"center",
display:'flex'
}
}
We can also use Stack component:
import Stack from '#mui/material/Stack';
<Stack alignItems="center">
<Pagination className={classes.pagination} count={props.totalPage} color='primary' />
</Stack>
margin: "auto" worked for me
root: {
margin: "auto",
},
I got the idea from here: 4 ways to center a component in Material-UI

Resources