FOUC when using #material-ui/core with NextJS/React - material-ui

My simple NextJS page looks like this (results can be viewed at https://www.schandillia.com/):
/* eslint-disable no-unused-vars */
import React, { PureComponent, Fragment } from 'react';
import Head from 'next/head';
import compose from 'recompose/compose';
import Layout from '../components/Layout';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const styles = {
root: {
textAlign: 'center',
paddingTop: 200,
},
p: {
textTransform: 'uppercase',
color: 'red',
},
};
class Index extends PureComponent {
render() {
const { classes } = this.props;
const title = 'Project Proost';
const description = 'This is the description for the homepage';
return (
<Fragment>
<Head>
<title>{ title }</title>
<meta name="description" content={description} key="description" />
</Head>
<Layout>
<p className={classes.p}>amit</p>
<Button variant="contained" color="secondary">
Secondary
</Button>
</Layout>
</Fragment>
);
}
}
export default withStyles(styles)(Index);
I am importing a bunch of components off the #material-ui/core library to style my items. I also have a local style definition assigned to a style constant.
What seems to be happening here is that my style isn't getting rendered on the server which is why the files being served upon load are sans-style. And then the CSS gets rendered by the client-side code. As a result, there's a flash of unstyled content that lasts almost a second, long enough to be noticable.
Any way to fix this? The entire codebase is up for reference at https://github.com/amitschandillia/proost/tree/master/web.

I ran a similar problem when tried to make a production build of my app, that uses material-ui. I manage to solve by adding a JSS Provider like this:
import JssProvider from "react-jss/lib/JssProvider";
class App extends Component {
render() {
<JssProvider>
*the rest of your material-ui components*
</JssProvider>
}
}

Here's the solution - https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_document.js .
Basically, all you need to do is to sync server-side class names with client-side. The link above shows what you need to do to fix that issue.

Related

Material UI > Backdrop > only for some subcomponent of the page

Is there any way how to enhance a backdrop from example in https://material-ui.com/components/backdrop/ to show loading circle only above the single component (in case some page has more component), not above the whole page?
Thanks for reply.
Backdrop are fixed positioned by default, that's why it covers the whole page.
To achieve the result you want, we have to change its position to absolute and contain it inside an element with relative position — this element can be your component. If you're new in CSS positions check this docs from developer.mozilla.org.
Knowing all that, we can come up with the following codes
const useStyles = makeStyles({
parent: {
position: "relative",
width: 200,
height: 200,
backgroundColor: "red",
zIndex: 0,
},
backdrop: {
position: "absolute"
}
});
export default function App() {
const classes = useStyles();
return (
<div className={classes.parent}>
<Backdrop className={classes.backdrop} open={true}>
<CircularProgress color="inherit" />
</Backdrop>
</div>
);
}
Also we have to define z-index on either parent or backdrop element to make it work. Not sure why though.
I created a codesandbox for you to play with.
The Backdrop component of Material UI is set to position: 'fixed' by default, that's why it covers the whole page.
If you want it to reside and position itself like any other component typically on the DOM, all you have to do is to reset its position back to relative, for instance:
<Backdrop open={true} sx={{ position: 'relative' }}>
<CircularProgress color="inherit" />
</Backdrop>
and you don't need to change the parent component since it should be in your case see to relative by default if you're not changing it. But if you have crazy positions going in your app here and there, then you might consider changing that as well.
i have created a custom componenet that i use if i want to block only part of the UI:
"use strict";
/** external libraries */
import React from "react";
import Backdrop from "#mui/material/Backdrop";
import CircularProgress from "#mui/material/CircularProgress";
const BlockUi = ({open, onClose, children}) => {
return (
<div style={{"position": "relative"}}>
<Backdrop
sx={{color: "#FFFFFF", zIndex: (theme) => theme.zIndex.drawer + 1, "position": "absolute"}}
open={open}
onClick={() => onClose()}
>
<CircularProgress color="inherit"/>
</Backdrop>
{children}
</div>
);
}
export default BlockUi;
and i use it like this:
"use strict";
/** external libraries */
import React from "react";
import BlockUi from "./BlockUi";
const JsonForm = ({fields, onSubmit}) => {
const [loading, setLoading] = React.useState(false)
const stopLoading = () => {
setLoading(false)
}
return (
<div>
<BlockUi open={loading} onClose={stopLoading}>
<button type="submit" onClick={() => {
console.log(loading)
setLoading(true)
}}>Submit
</button>
</BlockUi>
</div>
);
}
export default JsonForm;

Ionic-React: Not able to customize single IonSelectOption with CSS class

I'm trying to customize a single IonSelectOption with a custom CSS class to change the color of one IonSelectOption. I am copy-pasting the code from Ionic's documentation but it still isn't working. The CSS className that I provide to the IonSelectOption does not seem to get passed along to whatever interface that it selected. Hope someone can help me out!
Link to Documentation. This is the code I'm using:
import React from 'react';
import { IonContent, IonItem, IonLabel, IonSelect, IonSelectOption, IonPage } from '#ionic/react';
const options = {
cssClass: 'my-custom-interface'
};
export const SelectOptionExample: React.FC = () => {
return (
<IonPage>
<IonContent>
<IonItem>
<IonLabel>Select</IonLabel>
<IonSelect interface="popover" interfaceOptions={options}>
<IonSelectOption value="brown" class="brown-option">Brown</IonSelectOption>
<IonSelectOption value="blonde">Blonde</IonSelectOption>
<IonSelectOption value="black">Black</IonSelectOption>
<IonSelectOption value="red">Red</IonSelectOption>
</IonSelect>
</IonItem>
</IonContent>
</IonPage>
);
};
and the CSS:
/* Popover Interface: set color for the popover using Item's CSS variables */
.my-custom-interface .brown-option {
--color: #5e3e2c;
--color-hover: #362419;
}
in react, it is className, not class

MaterialUI CardMedia Image with spinner

The following component works perfectly file. I want to show a spinner until the image gets loaded. How do I do that?
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import Image from 'material-ui-image';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardContent from '#material-ui/core/CardContent';
import CardMedia from '#material-ui/core/CardMedia';
import Typography from '#material-ui/core/Typography';
/**
* Media is a Card, with an image / video and a caption. url of the media is hidden from the user,
* but the user can click it to open it in a new browser
*/
const useStyles = makeStyles({
card: {
margin: '0.5rem',
maxWidth: '25%'
}
});
const Media = ({ url, caption }: any) => {
const classes = useStyles();
return (
<Card className={classes.card}>
<CardActionArea>
<CardMedia component="img" alt={caption} height="140" image={url} title={caption} />
<CardContent>
<Typography variant="body2">{caption}</Typography>
</CardContent>
</CardActionArea>
</Card>
);
};
export default Media;
The image component should be loaded regardless of image is loaded or not. We can not show the image component on the screen by setting the height of the component to 0 and and then setting the height of the image component to desired height once the image is loaded.
Then, the tracking of if the image is loaded can be done using the state.
import React, { useState } from 'react';
// Image component with loading state
export default Image = () => {
const [hasImageLoaded, setHasImageLoaded] = useState(false);
return (
<>
<img src={src} onLoad={() => setHasImageLoaded(true)} className={`${!hasImageLoaded && height-0}`} />
{ !hasImageLoaded && <div> Loading... </div> }
</>
);
}
.height-0 {
height: 0;
}
Ps. This code can be changed according to your use. The code is done considering the general use
The Material-UI CardMedia component displays the image as a background image, so you won't be able to access the onLoad property available on the DOM <img> tag. You could chooses to use an <img> instead, like this:
add to you imports:
import CircularProgress from '#material-ui/core/CircularProgress';
add to your styles:
media: {
width: '100%',
height: 140,
},
progress: {
# center spinner
}
add state and onLoad handler to your component:
const [loaded, setLoaded] = React.useState(false);
function handleImageLoad() {
setLoaded(true);
}
replace your CardMedia with:
{loaded ? (
<img
className={classes.media}
src={url}
alt={caption}
onLoad={handleImageLoad}
/>
) : (
<div className={classes.progress}>
<CircularProgress color="secondary" />
</div>
)}

How to disable the SaveButton when the SimpleForm is invalid in a react-admin app?

In a react-admin SimpleForm component validation is working fine when I click the save button. The field that is required is highlighted and marked red when I click the save button.
I'd like to add a className to the SaveButton as long as the form is invalid. This way I can make it clear to the user that he's not done with the form yet and prevent the user from clicking it.
This is a simplified version of such a SimpleForm.
import {
required,
//...
} from 'react-admin';
const UserCreateToolbar = props =>
<Toolbar {...props}>
<SaveButton
label="user.action.save_and_show"
redirect="show"
submitOnEnter={true}
/>
</Toolbar>;
export const UserCreate = props =>
<Create {...props}>
<SimpleForm
toolbar={<UserCreateToolbar />}
>
<TextInput source="name" validate={[required()]} />
</SimpleForm>
</Create>;
You can create your own SaveButton component, connected to redux, which will get the validation status from the state (check the redux-form documentation) for the form which is always named record-form in react-admin.
Then, you can apply the disabled prop on the button and eventually tweak its styles
Here's my own SaveButton component I came up with. It's working for me. Thanks #Gildas for pointing me in the right direction.
import React from 'react';
import PropTypes from 'prop-types';
import { reduxForm } from 'redux-form';
import { SaveButton } from 'react-admin';
const SaveButtonAware = ({ invalid, ...rest }) => (
<SaveButton disabled={invalid} {...rest} />
);
SaveButtonAware.propTypes = {
invalid: PropTypes.bool,
};
export default reduxForm({
form: 'record-form',
})(SaveButtonAware);
Update. Apparently, this is working too. Not sure why.
import React from 'react';
import PropTypes from 'prop-types';
import { SaveButton } from 'react-admin';
const SaveButtonAware = ({ invalid, ...rest }) => (
<SaveButton disabled={invalid} {...rest} />
);
SaveButtonAware.propTypes = {
invalid: PropTypes.bool,
};
export default SaveButtonAware;
You should first create a customized toolbar and set Save button's disabled element to Toolbars invalid state, as shown below. ( Toolbar always have the state of form)
import * as React from "react";
import {
Toolbar,
SaveButton
} from 'react-admin';
const CustomToolbar = (props) => (
<Toolbar {...props}>
<SaveButton disabled={props.invalid}/>
</Toolbar>
);
export default CustomToolbar;
Then use this customized toolbar in your form like shown below:
<SimpleForm redirect="list" toolbar={<CustomToolbar/>}>
{your form elements}
</SimpleForm>

Support for a permanent clipped AppDrawer

I'm trying to make a permanent clipped navigation drawer with Material UI as per https://material.io/guidelines/patterns/navigation-drawer.html
Seems that there is a pull request out for this but not yet merged: https://github.com/callemall/material-ui/pull/6878
At this stage I'm trying to override with styles but can not get my left nav (paper) to apply the style marginTop: '50px',
Are there some samples out there on how to achieve this with v1.0.0-alpha.21?
They changed the way you have to override certain styles in v1. The inline styles no longer work. Certain parts of a component can be overridden with a simple className applied to the component. See this link for further details https://material-ui-1dab0.firebaseapp.com/customization/overrides.
Some deeper nested properties of certain components i.e the height of the Drawer can only be accessed by overriding the class itself. In this case the paper class of the drawer element.
This is a simple example
import React, { Component } from "react";
import Drawer from "material-ui/Drawer";
import { withStyles, createStyleSheet } from "material-ui/styles";
import PropTypes from 'prop-types';
const styleSheet = createStyleSheet("SideNav", {
paper: {
marginTop: '50px'
}
});
class SideNav extends Component {
....
render() {
return (
<Drawer
classes={{paper: this.props.classes.paper}}
docked={true}
>
....
</Drawer>
);
}
}
SideNav.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styleSheet)(SideNav);