Skip to content

Commit

Permalink
[docs] Add a next.js demo with hooks (#13920)
Browse files Browse the repository at this point in the history
* [docs] Add a next.js demo with hooks

* review
  • Loading branch information
oliviertassinari authored Dec 19, 2018
1 parent 37821b8 commit 9445b61
Show file tree
Hide file tree
Showing 14 changed files with 396 additions and 5 deletions.
3 changes: 3 additions & 0 deletions examples/nextjs-hooks/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"presets": ["next/babel"]
}
18 changes: 18 additions & 0 deletions examples/nextjs-hooks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
/node_modules

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Next.js
/.next
22 changes: 22 additions & 0 deletions examples/nextjs-hooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Next.js Hooks example

## How to use

Download the example [or clone the repo](https://github.com/mui-org/material-ui):

```bash
curl https://codeload.github.com/mui-org/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/nextjs
cd nextjs
```

Install it and run:

```bash
npm install
npm run dev
```

## The idea behind the example

[Next.js](https://github.com/zeit/next.js) is a framework for server-rendered React apps.
[Hooks](https://reactjs.org/docs/hooks-state.html) are an upcoming feature of React.
1 change: 1 addition & 0 deletions examples/nextjs-hooks/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
19 changes: 19 additions & 0 deletions examples/nextjs-hooks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "nextjs",
"version": "3.0.0",
"private": true,
"dependencies": {
"@material-ui/core": "latest",
"@material-ui/styles": "latest",
"jss": "latest",
"next": "latest",
"prop-types": "latest",
"react": "next",
"react-dom": "next"
},
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
}
52 changes: 52 additions & 0 deletions examples/nextjs-hooks/pages/_app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import '../src/bootstrap';
// --- Post bootstrap -----
import React from 'react';
import App, { Container } from 'next/app';
import Head from 'next/head';
import { StylesProvider, ThemeProvider } from '@material-ui/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import getPageContext from '../src/getPageContext';

class MyApp extends App {
constructor() {
super();
this.pageContext = getPageContext();
}

componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles && jssStyles.parentNode) {
jssStyles.parentNode.removeChild(jssStyles);
}
}

render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Head>
<title>My page</title>
</Head>
{/* Wrap every page in Styles and Theme providers */}
<StylesProvider
generateClassName={this.pageContext.generateClassName}
sheetsRegistry={this.pageContext.sheetsRegistry}
sheetsManager={this.pageContext.sheetsManager}
>
{/* ThemeProvider makes the theme available down the React
tree thanks to React context. */}
<ThemeProvider theme={this.pageContext.theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
{/* Pass pageContext to the _document though the renderPage enhancer
to render collected styles on server side. */}
<Component pageContext={this.pageContext} {...pageProps} />
</ThemeProvider>
</StylesProvider>
</Container>
);
}
}

export default MyApp;
99 changes: 99 additions & 0 deletions examples/nextjs-hooks/pages/_document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react';
import PropTypes from 'prop-types';
import Document, { Head, Main, NextScript } from 'next/document';
import flush from 'styled-jsx/server';

class MyDocument extends Document {
render() {
const { pageContext } = this.props;

return (
<html lang="en" dir="ltr">
<Head>
<meta charSet="utf-8" />
{/* Use minimum-scale=1 to enable GPU rasterization */}
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
/>
{/* PWA primary color */}
<meta
name="theme-color"
content={pageContext ? pageContext.theme.palette.primary.main : null}
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}

MyDocument.getInitialProps = ctx => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render

// Render app and page and get the context of the page with collected side effects.
let pageContext;
const page = ctx.renderPage(Component => {
const WrappedComponent = props => {
pageContext = props.pageContext;
return <Component {...props} />;
};

WrappedComponent.propTypes = {
pageContext: PropTypes.object.isRequired,
};

return WrappedComponent;
});

let css;
// It might be undefined, e.g. after an error.
if (pageContext) {
css = pageContext.sheetsRegistry.toString();
}

return {
...page,
pageContext,
// Styles fragment is rendered after the app and page rendering finish.
styles: (
<React.Fragment>
<style
id="jss-server-side"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: css }}
/>
{flush() || null}
</React.Fragment>
),
};
};

export default MyDocument;
40 changes: 40 additions & 0 deletions examples/nextjs-hooks/pages/about.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import '../src/bootstrap';
// --- Post bootstrap -----
import React from 'react';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/styles';
import Link from 'next/link';

const useStyles = makeStyles(theme => ({
root: {
textAlign: 'center',
paddingTop: theme.spacing.unit * 20,
},
}));

function About() {
const classes = useStyles();

return (
<div className={classes.root}>
<Typography variant="h4" gutterBottom>
Material-UI
</Typography>
<Typography variant="subtitle1" gutterBottom>
about page
</Typography>
<Typography gutterBottom>
<Link href="/">
<a>Go to the main page</a>
</Link>
</Typography>
<Button variant="contained" color="primary">
Do nothing button
</Button>
</div>
);
}

export default About;
64 changes: 64 additions & 0 deletions examples/nextjs-hooks/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import '../src/bootstrap';
// --- Post bootstrap -----
import React from 'react';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogActions from '@material-ui/core/DialogActions';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/styles';
import Link from 'next/link';

const useStyles = makeStyles(theme => ({
root: {
textAlign: 'center',
paddingTop: theme.spacing.unit * 20,
},
}));

function Index() {
const classes = useStyles();
const [open, setState] = React.useState(false);

const handleClose = () => {
setState(false);
};
const handleClick = () => {
setState(true);
};

return (
<div className={classes.root}>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Super Secret Password</DialogTitle>
<DialogContent>
<DialogContentText>1-2-3-4-5</DialogContentText>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={handleClose}>
OK
</Button>
</DialogActions>
</Dialog>
<Typography variant="h4" gutterBottom>
Material-UI
</Typography>
<Typography variant="subtitle1" gutterBottom>
example project
</Typography>
<Typography gutterBottom>
<Link href="/about">
<a>Go to the about page</a>
</Link>
</Typography>
<Button variant="contained" color="secondary" onClick={handleClick}>
Super Secret Password
</Button>
</div>
);
}

export default Index;
10 changes: 10 additions & 0 deletions examples/nextjs-hooks/src/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { install } from '@material-ui/styles';

// We will make @material-ui/styles the default style implementation
// for the core components in Material-UI v4.
// This installation step is temporary.
// Behind the scenes, the install() function switches the
// styling engine the core components use.
//
// https://material-ui.com/css-in-js/basics/#migration-for-material-ui-core-users
install();
54 changes: 54 additions & 0 deletions examples/nextjs-hooks/src/getPageContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* eslint-disable no-underscore-dangle */

import { SheetsRegistry } from 'jss';
import { createMuiTheme } from '@material-ui/core/styles';
import { createGenerateClassName } from '@material-ui/styles';
import purple from '@material-ui/core/colors/purple';
import green from '@material-ui/core/colors/green';

// A theme with custom primary and secondary color.
// It's optional.
const theme = createMuiTheme({
palette: {
primary: {
light: purple[300],
main: purple[500],
dark: purple[700],
},
secondary: {
light: green[300],
main: green[500],
dark: green[700],
},
},
typography: {
useNextVariants: true,
},
});

function createPageContext() {
return {
theme,
// This is needed in order to deduplicate the injection of CSS in the page.
sheetsManager: new Map(),
// This is needed in order to inject the critical CSS.
sheetsRegistry: new SheetsRegistry(),
// The standard class name generator.
generateClassName: createGenerateClassName(),
};
}

export default function getPageContext() {
// Make sure to create a new context for every server-side request so that data
// isn't shared between connections (which would be bad).
if (!process.browser) {
return createPageContext();
}

// Reuse context on the client-side.
if (!global.__INIT_MATERIAL_UI__) {
global.__INIT_MATERIAL_UI__ = createPageContext();
}

return global.__INIT_MATERIAL_UI__;
}
Loading

0 comments on commit 9445b61

Please sign in to comment.