I am using material UI with Next.js. I am running onnpm run dev. My problem is that the styling on the site completely breaks whenever I press the reloading button on the browser. Is this normal behavior? Seems like Material-UI stops working.
Here is my code.
I have an index.js and a component.
index
import React, { Component } from 'react'
import CssBaseline from '#material-ui/core/CssBaseline';
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles';
import AppBar from '../components/NavBar/NavBar';
const theme = createMuiTheme({
palette: {
primary: {
main: '#f28411',
},
},
});
class App extends Component {
render() {
return (
<MuiThemeProvider theme={theme}>
<React.Fragment>
<CssBaseline />
<AppBar />
</React.Fragment>
</MuiThemeProvider>
)
}
}
export default App
component
import React, { Component } from 'react'
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar'
import Toolbar from '#material-ui/core/Toolbar'
import Typography from '#material-ui/core/Typography'
class NavBar extends Component {
render() {
const { classes } = this.props;
return(
<div>
<AppBar position="static">
<Toolbar>
<Typography variant="title" color="inherit">
Test
</Typography>
</Toolbar>
</AppBar>
</div>
);
}
}
const styles = theme => ({
title: {
color: '#FFF',
},
});
NavBar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(NavBar);
I had this same issue but was not using styled-components so the above answer did not apply, so I thought I would post in case this helps anyone else. To fix this, I just had to add the _document.js file under the pages/ directory included in the material-ui sample:
import React from 'react'
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheets } from '#material-ui/core/styles'
import theme from '../shared/utils/theme'
export default class MyDocument extends Document {
render() {
return (
<Html lang='en'>
<Head>
<meta name='theme-color' content={theme.palette.primary.main} />
<link
rel='stylesheet'
href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets()
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
}
}
It could be possible that you need a babel plugin for this.
In your package, add
npm install --save-dev babel-plugin-styled-components
In your .babelrc, add
{
"plugins": [
[ "styled-components", { "ssr": true, "displayName": true, "preprocess": false } ]
]
}
Let me know if this works.
As it turns out, you cannot use all of the third-party libraries for Next JS in the same way as you would use for React apps. You need to modify your pages/_document.js and pages/_app.js. Also, you will need theme.js for configuring Material UI colors and other default styles. You can include theme.js to any folder, in my case it is in helpers folder.
_app.js
import React from "react";
import App from "next/app";
import theme from '../helpers/theme'; // needed for Material UI
import CssBaseline from '#material-ui/core/CssBaseline';
import {ThemeProvider} from '#material-ui/core/styles';
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
//Anything returned here can be accessed by the client
return {pageProps: pageProps};
}
render() {
const {Component, pageProps, router} = this.props;
return (
<ThemeProvider theme={theme}>
<CssBaseline />
{/* default by next js */}
<Component {...pageProps}/>
</ThemeProvider>
)
}
}
export default MyApp
_document.js
import Document, {Html, Head, Main, NextScript} from "next/document";
import theme from '../helpers/theme';
import { ServerStyleSheets } from '#material-ui/core/styles';
import React from "react";
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
let props = {...initialProps};
return props;
}
render() {
return (
<Html>
<Head>
<meta name="theme-color" content={theme.palette.primary.main} />
</Head>
<body>
<Main/>
<NextScript/>
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and register rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
theme.js
import { createMuiTheme } from '#material-ui/core/styles';
import { red } from '#material-ui/core/colors';
// Create a theme instance.
const theme = createMuiTheme({
palette: {
primary: {
main: '#FFF212',
},
secondary: {
main: '#FFF212',
},
error: {
main: red.A400,
},
background: {
default: '#fff',
},
},
});
export default theme;
There is an amazing repository in Github https://github.com/vercel/next.js/tree/canary/examples
There you can find a list of libraries with the correct way of using them in Next JS apps.
For your case here is the link: https://github.com/vercel/next.js/tree/canary/examples/with-material-ui.
Related
I am working on a NextJS site that has some custom styling that is being applied via MaterialUI's makeStyles. It works fine on the first load and then undoes all of the custom work on the second. It seems that it has something to do with the route as the only time that it works is when I first am directed to the page itself. It is happening on 2 different pages one is being directed via href='/login'and the other is being directed via next/router router.push('/register')
I am assuming that this has to do with the way that Next loads the page? But I will say that both of these are prerendered pages according to the icon at the bottom right.
import React, {useState} from 'react';
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { login } from '../store/user/action'
import { useRouter } from 'next/router'
import TextField from '#material-ui/core/TextField';
import { makeStyles } from '#material-ui/core/styles';
const style = makeStyles({
root: {
marginBottom: '20px',
textAlign: 'center'
},
});
function Signin({login}) {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const router = useRouter();
const clickRegister = (e) => {
e.preventDefault();
router.push('/register')
}
const classStyle = style();
return (
<div className='flex flex-column center m-20 w-750'>
<h3 className='m-20'>Login</h3>
<form className='flex flex-column w-750 center' onSubmit={e=>login(e, {email, password})} >
<TextField
className={classStyle.root}
required
type='email'
id="email"
label="Email"
variant="outlined"
onChange={e=>setEmail(e.target.value)}
/>
<TextField
className={classStyle.root}
required
type='password'
id="password"
label="Password"
variant="outlined"
onChange={e=>setPassword(e.target.value)}
/>
<input
type='submit'
className='purple-button mt-20 h-3'
onClick={e=>login(e)}
value='Login' />
</form>
<p>Don't Have an account?</p>
<form onSubmit='/register'>
<input value='Register' type='submit' className='flex flex-column w-750 center purple-button h-3' onClick={e=>clickRegister(e)} />
</form>
</div>
)
}
const mapStateToProps = (state) => ({
email: state.user.email,
token: state.user.token,
isLoggedIn: state.user.isLoggedIn,
})
const mapDispatchToProps = (dispatch) => {
return {
login: bindActionCreators(login, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Signin)
"dependencies": {
"#material-ui/core": "4.11.3",
"axios": "0.21.1",
"material-ui": "0.20.2",
"next": "9.4.1",
"next-redux-wrapper": "^6.0.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "7.1.3",
"redux": "4.0.5",
"redux-devtools-extension": "2.13.8",
"redux-thunk": "2.3.0"
},
You need additional setup for Material-UI stylesheets in pages/_document.js to support SSR styling.
From MaterialUI v5
If you don't have a custom _document yet, create one with the following code:
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import createEmotionServer from '#emotion/server/create-instance';
import theme from '../src/theme';
import createEmotionCache from '../src/createEmotionCache';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => function EnhanceApp(props) {
return <App emotionCache={cache} {...props} />;
}
});
const initialProps = await Document.getInitialProps(ctx);
const emotionStyles = extractCriticalToChunks(initialProps.html);
const emotionStyleTags = emotionStyles.styles.map((style) => (
<style
data-emotion={`${style.key} ${style.ids.join(' ')}`}
key={style.key}
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: style.css }}
/>
));
return {
...initialProps,
emotionStyleTags,
};
}
render() {
return (
<Html lang="en">
<Head>
<meta name="theme-color" content={theme.palette.primary.main} />
<link rel="shortcut icon" href="/static/favicon.ico" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
{this.props.emotionStyleTags}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
};
Then, in your _app.
import React from 'react';
import Head from 'next/head';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { CacheProvider } from '#emotion/react';
import theme from '../src/theme';
import createEmotionCache from '../src/createEmotionCache';
const clientSideEmotionCache = createEmotionCache();
export default function MyApp(props) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
Before MaterialUI v5
If you don't have a custom _document.js yet, create one with the following code:
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '#material-ui/core/styles';
class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />)
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement()
]
};
}
render() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
You can then remove the server-side injected styles in your custom _app.js.
useEffect(() => {
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
For further details check Material-UI official documentation.
I have copied over the _document.js from the next styled components example and I am using the babel plugin from the styled components docs, but I am still getting the error.
_document.js
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
.babelrc
{
"presets": [
"next/babel"
],
"plugins": [
[
"babel-plugin-styled-components"
]
]
}
Just started using next on wednesday so still fairly new with the technology, but I've looked through all the docs and this seems to be what is supposed to work, but I am still getting the error, any thoughts?
You aren't rendering() then returning () any jsx. That's your issue here most likely. You should also be importing Html, Head, Main, and NextScript from "next/document".
To resolve your issue, try refactoring as follows:
import Document { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
};
} finally {
sheet.seal()
}
}
render() {
return (
<Html lang='en'>
<Head>
<meta charSet='utf-8' />
{this.props.styles}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
Then in _app.jsx you will import { ThemeProvider } from styled-components. ThemeProvider wraps the custom styled-components GlobalTheme JSX Element imported from another file. The following snippet is in typescript from a project I built a couple months ago. Most is relevant with the exception of the AppProps tidbit:
import Head from "next/head";
import { AppProps } from "next/app";
import { ThemeProvider } from "styled-components";
import { GlobalStyle, theme } from "../shared";
export default function App({ Component, pageProps }: AppProps) {
return (
<ThemeProvider theme={theme}>
<GlobalStyle theme={theme} />
<Head>
<title>Next.js</title>
</Head>
<main className="main">
<Component {...pageProps} />
</main>
</ThemeProvider>
)
}
I am using react navigation as per the docs but trying to make my app a bit more modular. I placed the result of createStackNavigator into a separate component..
Navigator.js
import React, { Component } from 'react';
import {createAppContainer} from 'react-navigation';
import {createStackNavigator} from 'react-navigation-stack';
import Home from './views/Home.js';
import TestComponent from './views/TestComponent.js';
const MainNavigator = createStackNavigator({
Home: {screen: Home},
Test: {screen: TestComponent}
});
export default createAppContainer(MainNavigator);
..and importing this component into my App.js
App.js
import React, { Component } from 'react';
import { View } from 'react-native';
import Header from './Header.js';
import Navigator from './Navigator.js';
import FooterMenu from './FooterMenu.js';
class App extends Component {
render() {
return (
<View>
<Header />
<Navigator />
<FooterMenu />
</View>
);
}
}
export default App;
My index.js is as follows:
import { AppRegistry } from 'react-native';
import App from './components/App';
import {name as appName} from './app.json';
AppRegistry.registerComponent(appName, () => App);
Im finding that my <Header/> and <FooterMenu/> components are rendering but the <Navigator/> component is not.
I found that if I replace the top-level <View> component with a React fragment, it does render.
render() {
return (
<>
<Header />
<Navigator />
<FooterMenu />
</>
);
}
Although this syntax breaks my editor's (sublime) syntax highlighting. Though if I change the fragment to <React.Fragment> React native throws an exception.
My questions are:
Why does <Navigator/> not render if I wrap it in a <View> component?
Why do I get an error if I use <React.Fragment>?
If you want to create your own Navigator, here's how.
It is possible to take an existing Navigator and extend its behavior, using the following approach:
const MyStack = createStackNavigator({ ... });
class CustomNavigator extends React.Component {
static router = MyStack.router;
render() {
const { navigation } = this.props;
return <MyStack navigation={navigation} />;
}
}
Now it is possible to render additional things, observe the navigation prop, and override behavior of the router:
const MyStack = createStackNavigator({ ... });
class CustomNavigator extends React.Component {
static router = {
...MyStack.router,
getStateForAction: (action, lastState) => {
// check for custom actions and return a different navigation state.
return MyStack.router.getStateForAction(action, lastState);
},
};
componentDidUpdate(lastProps) {
// Navigation state has changed from lastProps.navigation.state to this.props.navigation.state
}
render() {
const { navigation } = this.props;
return (
<View>
<Header />
<MyStack navigation={navigation} />
<FooterMenu />
</View>
);
}
}
If you want to know more about this,
I've a problem to integrate the theming functionality inside my next.js project who use react-jss. I tried the ThemeProvider who I've found inside the documentation.
Everytime, my front-end page refresh two times. The first times, I can see that the CSS theme is apply but fews milliseconds later, the page refresh and the theme disappear.
Do you have an idea to fix my problem? Here are my files:
_document.jsx
import React from 'react';
import Document, { Head, Main, NextScript } from 'next/document';
import {
SheetsRegistry,
JssProvider,
ThemeProvider,
} from 'react-jss';
const theme = {
colorPrimary: 'green',
};
export default class JssDocument extends Document {
static getInitialProps(ctx) {
const registry = new SheetsRegistry();
const page = ctx.renderPage(App => props => (
<ThemeProvider theme={theme}>
<JssProvider registry={registry}>
<App {...props} />
</JssProvider>
</ThemeProvider>
));
return {
...page,
registry,
};
}
render() {
return (
<html lang="en">
<Head>
<style id="server-side-styles">
{this.props.registry.toString()}
</style>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
index.jsx
import React from 'react';
import injectSheet from 'react-jss';
import PropTypes from 'prop-types';
const styles = theme => ({
myButton: {
backgroundColor: 'black',
color: theme.colorPrimary,
},
});
const Index = ({ classes }) => (
<div className={classes.myButton}>Welcome to Novatopo website!</div>
);
Index.propTypes = {
classes: PropTypes.shape({
myButton: PropTypes.string,
}).isRequired,
};
export default injectSheet(styles)(Index);
_app.jsx
import App from 'next/app';
export default class MyApp extends App {
componentDidMount() {
const style = document.getElementById('server-side-styles');
if (style) {
style.parentNode.removeChild(style);
}
}
}
Here a CodeSandbox to reproduce the problem: codesandbox.io/s/pyrznxkr1j
2 things you could do:
prepare a codesandbox example
use ThemeProvider inside of JssProvider
I resolved the problem. You need to integrate the ThemeProvider logic inside the _app.jsx file.
Like that:
export default class MyApp extends App {
render() {
const { Component, pageProps } = this.props;
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
);
}
}
I have the following component, that is build with https://material-ui-next.com/.
import React from 'react';
import { AppBar, Toolbar } from 'material-ui';
import { Typography } from 'material-ui';
import { MuiThemeProvider, createMuiTheme } from 'material-ui/styles';
import {lightBlue} from 'material-ui/colors';
const theme = createMuiTheme({
palette: {
primary: {
main:lightBlue['A700']
},
text: {
primary: '#fff',
}
},
});
const View = (props) => (
<MuiThemeProvider theme={theme}>
<AppBar position="static">
<Toolbar>
<Typography variant="title">
{props.title}
</Typography>
</Toolbar>
</AppBar>
</MuiThemeProvider>
);
export default View;
I am trying to write a test for it:
import React from 'react';
import { shallow } from 'enzyme';
import View from '../Views/View';
import { Typography } from 'material-ui';
it('renders', () => {
const wrapper = shallow(<View title='Welcome' />);
expect(wrapper.find('Typography').text()).toEqual('Welcome');
});
How to write a test for a component, that is using material-ui components? In the case above, I tried to figure out, if the component contains Welcome.
I read https://material-ui-next.com/guides/testing/, but it is not clear, how can I write a test.
UPD: API changed from shallow to mount
Did you tried to use their API described here?
Probably your test can look something like this:
import React from 'react';
import { createMount } from 'material-ui/test-utils';
import View from '../Views/View';
import { Typography } from 'material-ui';
it('renders', () => {
const mount = createMount();
const wrapper = mount(<View title='Welcome' />);
expect(wrapper.find('Typography').text()).toEqual('Welcome');
});