Next.js - Browser back gives--- TypeError: Cannot read property 'split' of undefined - reactjs

My issue is similar to this..
https://github.com/zeit/next.js/issues/5604
I am using a custom server but I am not using any custom route handling. Even if remove the custom server and only run next i get this error while navigating back using browser back button.
As mentioned in https://github.com/zeit/next.js/blob/canary/errors/popstate-state-empty.md
I am not manipulating window.history in any place. Still I am getting this error.
I am using next/router for routing.
This is the _app.js code.
import React from 'react';
import App from 'next/app';
import Router from 'next/router';
import Head from 'next/head';
import withRedux from 'withRedux';
import { Provider } from 'redux-bundler-react';
import { ThemeProvider } from 'emotion-theming';
import { Global } from '#emotion/core';
import themeOne from 'ui-web/theme';
import { getCookie } from 'modules/authentication';
import configureStore from '../../src/store';
import { persist, cacheVersions } from '../../src/common';
import { appWithTranslation } from '../../i18n';
const makeStore = initialState => configureStore(initialState);
class MyApp extends App {
static async getInitialProps(props) {
const { Component, ctx, router } = props;
if (ctx.isServer && ctx.req.headers.cookie) {
const token = getCookie('authToken', ctx.req);
ctx.store.doSetAuthToken(token);
}
const pageProps = Component.getInitialProps
? await Component.getInitialProps(ctx, router.pathname)
: {};
return { pageProps };
}
render() {
const { Component, store, pageProps } = this.props;
return (
<Provider store={store}>
<ThemeProvider theme={themeOne}>
<Head>
<title>Learny</title>
<link
href='https://fonts.googleapis.com/css?family=Open+Sans:300,400,600&display=swap'
rel='stylesheet'
/>
</Head>
<Global
styles={theme => ({
body: {
margin: 0,
overflowX: 'hidden',
backgroundColor: theme.colors.background,
a: {
textDecoration: 'none',
},
},
})}
/>
<Component {...pageProps} />
</ThemeProvider>
</Provider>
);
}
}
export default withRedux(makeStore, { debug: false, persist, cacheVersions })(
appWithTranslation(MyApp)
);
server.js code sample is
/* eslint-disable #typescript-eslint/no-var-requires */
const express = require('express');
const next = require('next');
const nextI18NextMiddleware = require('next-i18next/middleware').default;
const nextI18next = require('./i18n');
const port = process.env.PORT || 3000;
const app = next({ dev: process.env.NODE_ENV !== 'production' });
const handle = app.getRequestHandler();
(async () => {
await app.prepare();
const server = express();
server.use(nextI18NextMiddleware(nextI18next));
server.all('*', (req, res) => handle(req, res));
await server.listen(port);
console.log(`> Ready on http://localhost:${port}`);
})();

Related

Custom react-testing-library test setup causes tests to hang indefinitely on API calls

I'm using Jest to do some integration testing on my React App (AWS, Redux Toolkit, MSW).
I have a custom setup files that I am using in my tests as per react-testing-library instructions (https://testing-library.com/docs/react-testing-library/setup/).
testsUtils.js
import { App } from 'containers';
import { BrowserRouter } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
import { configureStore } from '#reduxjs/toolkit';
import i18n from './i18n';
import { persistedReducer } from 'store';
import { render } from '#testing-library/react';
const getStore = (store) => {
const str = { ...store };
return configureStore({
preloadedState: {
...str,
},
reducer: persistedReducer,
});
};
const renderTestApp = ({ route = '/', store }) => {
const str = getStore(store);
window.history.replaceState({}, '', route);
return render(
<Provider store={str}>
<BrowserRouter>
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>
</BrowserRouter>
</Provider>,
);
};
export * from '#testing-library/react';
export { renderTestApp as render };
This file allow me to render the app in a test, pass a route and an initial store to test a specific screen.
However, when an API call is made (I'm mocking the AWS requests using MSW) this warning shows up :
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks. Active timers can also cause this, ensure that .unref() was called on them.
On top of that, running 42 tests from 5 tests suites is really slow the first time, it takes around 180 seconds to complete and there are been instances where launching the tests plainly freezes my machine.
FileSelection.test.js
import { render, screen, waitFor } from 'testsUtils';
import userEvent from '#testing-library/user-event';
describe('FileSelection', () => {
beforeEach(async () => {
render({
route:
'/selection/human?id=kixUziFOWKpBhnTrknaA6w&des=SUM',
store: {
users: {
secondary: 'MSW msw',
main: 'JSW jsw',
},
files: {},
},
});
const title = await screen.findByText('Selection');
expect(title);
});
test('FileSelection screen renders', async () => {
await waitFor(() => {
expect(screen.getByText('Selection')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Continue' }),
).toBeInTheDocument();
});
});
}
App.js
import {
FileSelection,
} from 'containers';
import { Route, Routes } from 'react-router-dom';
import styled from '#emotion/styled';
import useApp from './hook/useApp';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
const AppLoading = styled.div`
align-items: center;
display: flex;
height: 100vh;
justify-content: center;
`;
const AppContainer = styled.div`
flex: 1;
`;
const App = (props) => {
const {
location,
processing,
} = useApp(props);
const { t } = useTranslation();
return (
<ThemeProvider>
{processing ? (
<AppLoading>
<Loading size={240} />
</AppLoading>
) : (
<AppContainer>
<Routes>
{tabsList.map((tab, index) => {
return (
<Route
element={
<Selection/>
}
key={t('Selection.path', {
slug: tab.slug,
})}
path={t('Selection.path', {
slug: tab.slug,
})}
/>
);
})}
</Routes>
</AppContainer>
)}
</ThemeProvider>
);
export default App;
useApp.js
import {
apiGetData,
getId,
} from 'store';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router';
import _ from 'lodash';
import slugify from 'slugify';
import styled from '#emotion/styled';
import { useTranslation } from 'react-i18next';
const BoExchangeTitle = styled.div`
flex: 1;
`;
const BoExchangeTitleContainer = styled.div`
align-items: flex-end;
display: flex;
justiycontent: space-between;
`;
const useApp = (props) => {
const dispatch = useDispatch();
const headings = useSelector(getHeadings);
const location = useLocation();
const navigate = useNavigate();
const id =
props?.id || useSelector(getId);
const { t } = useTranslation();
const [processing, setProcessing] = useState(false);
useEffect(async () => {
const queryParams = new URLSearchParams(location.search);
const searchId = queryParams.get('id');
const des = props?.des || queryParams.get('des');
let i = id;
if (searchId && searchId !== id) {
i = searchId;
}
if (destination) {
dispatch(setRecipient(destination));
}
if (i) {
try {
setProcessing(true);
await dispatch(apiGetData({ id })); //Api Call right here
dispatch(setId(id));
setProcessing(false);
} catch (error) {
setProcessing(false);
navigate(t('TechnicalError.path'));
console.log(error);
}
} else {
navigate(t('TechnicalError.path'));
}
}, []);
return {
location,
processing,
};
};
export default useApp;
apiGetData
export const apiGetData = ({ id }) => {
return async (dispatch) => {
try {
const response = await API.get(
"Endpoint gateway name",
"Endpoint path",
{
headers: {
'private key': 'private key value',
},
response: true,
},
);
if (response.status === 200) {
const { data } = response;
return data;
}
} catch (error) {
const { data, status, statusText } = error.response || {};
console.log('error', error);
throw {
data,
status,
statusText,
};
}
};
};
To me, it looks like this mocked AWS call is not detected as finished which causes the performance issues... Any idea on how to tackle this ? (Sorry if some code is missing, I had to cut a lot since I can't share the original one).
To clarify, the application is working fine on it's own, the tests are the issue.
I tried commenting the API call in useApp.js which makes the test run as expected. The test run fast and no warning logs in the console.
I tried running the tests with --runInBand and --detectOpenHandles which didn't print anything useful.
I tried disabling MSW which didnt change anything as expected.

After building nextjs app, chunk/pages/_app-.....js file size is too large

I have a next js project that is created by create next app and modified _app.ts to this
import "../../public/assets/css/bootstrap.css";
import "antd/dist/antd.css";
import "../../public/assets/css/common.css";
import "../styles/globals.css";
import "../styles/builderGlobals.css";
import "../../public/assets/css/quiz.css";
import "../../public/assets/css/main.css";
import "../../public/assets/css/responsive.css";
import Head from "next/head";
import { wrapper } from "../app/store";
import { setUser } from "../Modules/Auth/authSlice";
import Layout from "../components/Layouts/layout";
import type { AppPropsWithLayout } from "../utils/types";
import { setNotifSettingsData } from "../Modules/Notifications/notificationsSlice";
import serverApi from "../utils/axios/serverApi";
import NextNProgress from "nextjs-progressbar";
import { useAppSelector } from "../app/hooks";
const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
const favicon = useAppSelector(state => state.settingsData.favico_icon);
const getLayout =
Component.getLayout ??
((page) => (
<Layout>
<Head>
<link rel="shortcut icon" href={favicon || "/img/favicon.png"} />
</Head>
<NextNProgress /> {page}
</Layout>
));
return getLayout(<Component {...pageProps} />);
};
MyApp.getInitialProps = wrapper.getInitialAppProps(
(store) =>
async ({ Component, ctx }) => {
if (!ctx.req) {
return {
pageProps: {
...(Component.getInitialProps
? await Component.getInitialProps({ ...ctx, store })
: {}),
pathname: ctx.pathname,
},
};
}
try {
const { data: initialData } = await serverApi(
ctx,
`/settings/get-initial-site-data`
);
store.dispatch(setUser(initialData?.authUser));
store.dispatch(setNotifSettingsData(initialData?.siteSettings));
return {
pageProps: {
...(Component.getInitialProps
? await Component.getInitialProps({ ...ctx, store })
: {}),
pathname: ctx.pathname,
},
};
} catch (error) {
ctx.res.statusCode = 404;
ctx.res.end("Not found");
return;
}
}
);
export default wrapper.withRedux(MyApp);
But after running yarn build it created too large _app chunk. about 431kb
That is huge. How can I reduce this chunk? or am I doing anything wrong?
https://github.com/sakib412/sakib412/raw/main/WhatsApp%20Image%202022-10-13%20at%206.48.08%20PM.jpeg

NextJS: How to add screen loading for production build?

I want add screen loading in next js project. And I tried to do that with the Router component in next/router.
This is my _app.js in next.js project:
import {CookiesProvider} from 'react-cookie';
import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import withRedux from 'next-redux-wrapper'
import withReduxSaga from 'next-redux-saga'
import createStore from '../src/redux/store'
import Router from "next/router";
import {Loaded, Loading} from "../src/util/Utils";
class MyApp extends App {
static async getInitialProps({Component, ctx}) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ctx})
}
return {pageProps}
}
render() {
Router.onRouteChangeStart = () => {
Loading()
};
Router.onRouteChangeComplete = () => {
Loaded()
};
Router.onRouteChangeError = () => {
Loaded()
};
const {Component, pageProps, store} = this.props;
return (
<CookiesProvider>
<Provider store={store}>
<Component {...pageProps} />
</Provider>
</CookiesProvider>
)
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
This is Loaded() and Loading() functions:
export const Loaded = () => {
setTimeout(() => {
let loading = 'has-loading';
document.body.classList.remove(loading);
}, 100);
};
export const Loading = () => {
let loading = 'has-loading';
document.body.classList.add(loading);
};
The code works well when the project is under development mode. But when the project is built, the loading won't disappear.
Do you know solution of this issue or are you suggesting another solution?
Using apollo client and react hooks you could do as follow.
Example:
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag';
import { withApollo } from '../lib/apollo';
import UserCard from '../components/UserCard';
export const USER_INFO_QUERY = gql`
query getUser ($login: String!) {
user(login: $login) {
name
bio
avatarUrl
url
}
}
`;
const Index = () => {
const { query } = useRouter();
const { login = 'default' } = query;
const { loading, error, data } = useQuery(USER_INFO_QUERY, {
variables: { login },
});
if (loading) return 'Loading...'; // Loading component
if (error) return `Error! ${error.message}`; // Error component
const { user } = data;
return (
<UserCard
float
href={user.url}
headerImg="example.jpg"
avatarImg={user.avatarUrl}
name={user.name}
bio={user.bio}
/>
);
};
export default withApollo({ ssr: true })(Index);
More info here: https://github.com/zeit/next.js/tree/canary/examples/with-apollo
I added the following codes to a wrapper component and the problem was resolved.
componentDidMount() {
Loaded();
}
componentWillUnmount() {
Loading();
}

Prevent client side re-render when using SSR and Apollo client

Problem in a nutshell is I server side render an html doc then the React app hydrates and re-renders what is already there. After that point the app works client side just great.
I am using React, Apollo Client (Boost 0.3.1) , Node, Express, and a graphql server we have in house.
See this in action here: https://www.slowdownshow.org/
Mostly I have tried what is suggested in the docs:
https://www.apollographql.com/docs/react/features/server-side-rendering
Here is what is not clear. Am I to assume that if I implement Store Rehydration the Apollo Client xhr request to fetch the data will not need to happen? If so the problem is I've tried what the docs suggest for store rehydration, but the doc is a little ambiguous
<script>
window.__APOLLO_STATE__ = JSON.stringify(client.extract());
</script>
What is client in this case? I believe it is the ApolloClient. But it is a method not an object, if I use that here I get error messages like
Warning: Failed context type: Invalid contextclientof typefunctionsupplied toComponent, expectedobject.
If the Store Rehydration technique is not the way to prevent unnecessary client side re-renders - it's not clear to me what is.
Here is the relevant server code:
import React from 'react';
import ReactDOM from 'react-dom/server';
import { ApolloProvider, renderToStringWithData } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import FragmentMatcher from '../shared/graphql/FragmentMatcher';
import { HelmetProvider } from 'react-helmet-async';
import { ServerLocation } from 'apm-titan';
import App from '../shared/App';
import fs from 'fs';
import os from 'os';
import {
globalHostFunc,
replaceTemplateStrings,
isFresh,
apm_etag,
siteConfigFunc
} from './utils';
export default function ReactAppSsr(app) {
app.use((req, res) => {
const helmetContext = {};
const filepath =
process.env.APP_PATH === 'relative' ? 'build' : 'current/build';
const forwarded = globalHostFunc(req).split(':')[0];
const siteConfig = siteConfigFunc(forwarded);
const hostname = os.hostname();
const context = {};
const cache = new InMemoryCache({ fragmentMatcher: FragmentMatcher });
let graphqlEnv = hostname.match(/dev/) ? '-dev' : '';
graphqlEnv = process.env.NODE_ENV === 'development' ? '-dev' : graphqlEnv;
const graphqlClient = (graphqlEnv) => {
return new ApolloClient({
ssrMode: false,
cache,
link: createHttpLink({
uri: `https://xxx${graphqlEnv}.xxx.org/api/v1/graphql`,
fetch: fetch
})
});
};
let template = fs.readFileSync(`${filepath}/index.html`).toString();
const component = (
<ApolloProvider client={graphqlClient}>
<HelmetProvider context={helmetContext}>
<ServerLocation url={req.url} context={context}>
<App forward={forwarded} />
</ServerLocation>
</HelmetProvider>
</ApolloProvider>
);
renderToStringWithData(component).then(() => {
const { helmet } = helmetContext;
let str = ReactDOM.renderToString(component);
const is404 = str.match(/Not Found\. 404/);
if (is404?.length > 0) {
str = 'Not Found 404.';
template = replaceTemplateStrings(template, '', '', '', '');
res.status(404);
res.send(template);
return;
}
template = replaceTemplateStrings(
template,
helmet.title.toString(),
helmet.meta.toString(),
helmet.link.toString(),
str
);
template = template.replace(/__GTMID__/g, `${siteConfig.gtm}`);
const apollo_state = ` <script>
window.__APOLLO_STATE__ = JSON.stringify(${graphqlClient.extract()});
</script>
</body>`;
template = template.replace(/<\/body>/, apollo_state);
res.set('Cache-Control', 'public, max-age=120');
res.set('ETag', apm_etag(str));
if (isFresh(req, res)) {
res.status(304);
res.send();
return;
}
res.send(template);
res.status(200);
});
});
}
client side:
import App from '../shared/App';
import React from 'react';
import { hydrate } from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import { HelmetProvider } from 'react-helmet-async';
import { client } from '../shared/graphql/graphqlClient';
import '#babel/polyfill';
const graphqlEnv = window.location.href.match(/local|dev/) ? '-dev' : '';
const graphqlClient = client(graphqlEnv);
const Wrapped = () => {
const helmetContext = {};
return (
<HelmetProvider context={helmetContext}>
<ApolloProvider client={graphqlClient}>
<App />
</ApolloProvider>
</HelmetProvider>
);
};
hydrate(<Wrapped />, document.getElementById('root'));
if (module.hot) {
module.hot.accept();
}
graphqlCLinet.js:
import fetch from 'cross-fetch';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import FragmentMatcher from './FragmentMatcher';
const cache = new InMemoryCache({ fragmentMatcher: FragmentMatcher });
export const client = (graphqlEnv) => {
return new ApolloClient({
ssrMode: true,
cache,
link: createHttpLink({
uri: `https://xxx${graphqlEnv}.xxx.org/api/v1/graphql`,
fetch: fetch
})
});
};
FragmentMatcher.js:
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
const FragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData: {
__schema: {
types: [
{
kind: 'INTERFACE',
name: 'resourceType',
possibleTypes: [
{ name: 'Episode' },
{ name: 'Link' },
{ name: 'Page' },
{ name: 'Profile' },
{ name: 'Story' }
]
}
]
}
}
});
export default FragmentMatcher;
See client side re-renders in action
https://www.slowdownshow.org/
In the production version of the code above,
I skip state rehydration window.__APOLLO_STATE__ = JSON.stringify(${graphqlClient.extract()}); as I do not have it working
So the answer was simple once I realized I was making a mistake. I needed to put
window.__APOLLO_STATE__ = JSON.stringify(client.extract());
</script>
BEFORE everything else so it could be read and used.
This const apollo_state = ` <script>
window.__APOLLO_STATE__ = JSON.stringify(${graphqlClient.extract()});
</script>
</body>`;
template = template.replace(/<\/body>/, apollo_state);
needed to go up by the <head> not down by the body. Such a no duh now but tripped me up for a while

next.js & material-ui - getting them to work

I'm giving next.js a spin and I can't get the simplest setup to work.
Here's my setup:
Relevant libs:
"react": "^16.2.0",
"react-dom": "^16.2.0",
"next": "^4.2.2",
"express": "^4.16.2",
"next-routes": "^1.2.0",
"material-ui": "^0.20.0",
server.js
const express = require('express')
const next = require('next');
const routes = require('./routes');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handler = routes.getRequestHandler(app, ({req, res, route, query}) => {
render(req, res, route.page, query);
});
const server = express();
app.prepare()
.then(() => {
server.use(handler).listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
routes.js
const routes = module.exports = require('next-routes')();
routes
.add({name: 'walk', pattern: '/walk/:id'})
_document.js
import Document, { Head, Main, NextScript } from 'next/document';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
export default class extends Document {
static async getInitialProps(ctx) {
const props = await Document.getInitialProps(ctx);
const userAgent = ctx.req.headers['user-agent'];
return {
...props,
userAgent,
};
}
render() {
return (
<html>
<Head>
<title>ShareWalks</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<link rel="stylesheet" href="https://sharewalks.com/shared.css" />
</Head>
<body>
<MuiThemeProvider muiTheme={getMuiTheme({ userAgent: this.props.userAgent })}>
<div>
<Main />
<NextScript />
</div>
</MuiThemeProvider>
</body>
</html>
);
}
}
pages/index.js (this works)
import React, {Component} from 'react';
import Head from 'next/head';
class App extends Component {
static async getInitialProps(args) {
return {};
}
render() {
return (
<div>
<Head>
<title>ShareWalks</title>
</Head>
<p>Yup</p>
</div>
);
}
}
export default App;
pages/walk.js (it errors here)
import React, {Component} from 'react';
import {Head} from 'next/head';
class Walk extends Component {
static async getInitialProps({query}) {
console.log('query: ', query);
return {id: query.id}; //added to props
}
render() {
return (
<div>
<Head>
<title>Walking</title>
</Head>
<p>{`Walk #${this.props.id}`}</p>
</div>
);
}
}
export default Walk;
When I go to localhost:8080/walk/2 or localhost:8080/walk?id=2 I get the error. The console does print out the id as expected, but then this:
query: { id: '2' }
TypeError: Cannot read property 'toLowerCase' of undefined
at a.renderDOM (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:390)
at a.render (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:36:228)
at a.read (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:35:250)
at renderToString (/home/terry/myProjects/PWA/sw-next/node_modules/react-dom/cjs/react-dom-server.node.production.min.js:44:6)
at renderPage (/home/terry/myProjects/PWA/sw-next/node_modules/next/dist/server/render.js:174:26)
at Function.getInitialProps (/home/terry/myProjects/PWA/sw-next/node_modules/next/dist/server/document.js:83:25)
at Function._callee$ (/home/terry/myProjects/PWA/sw-next/.next/dist/pages/_document.js:138:59)
at tryCatch (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:62:40)
at Generator.invoke [as _invoke] (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:296:22)
at Generator.prototype.(anonymous function) [as next] (/home/terry/myProjects/PWA/sw-next/node_modules/regenerator-runtime/runtime.js:114:21)
First guess is to try this with your server file - the biggest issue is that you aren't setting the id param on the route itself, so on the client side - it doesn't know to look for ID.
try this:
const express = require('express')
const next = require('next')
const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare()
.then(() => {
const server = express()
server.get('/walk/:id', (req, res) => {
return app.render(req, res, '/walk', { id: req.params.id })
})
server.get('*', (req, res) => {
return handle(req, res)
})
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)
})
})
OK, getting material-ui to work with Next.js is for professionals. As far as I can tell, you can't use _document.js to set up material-ui. Can someone tell me why? Anyway, I wrote a higher order component thusly:
/components/hocs/withMui.js
import React, {Component} from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import injectTapEventPlugin from 'react-tap-event-plugin' //still needed?
import myTheme from 'styles/theme'
const muiTheme = myTheme;
export default function(NextPage) {
class outputComponent extends Component {
static async getInitialProps(ctx) {
const {req} = ctx;
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent;
let pageProps = {};
if (NextPage.getInitialProps) {
pageProps = await NextPage.getInitialProps(ctx);
}
return {
...pageProps,
userAgent
}
}
render() {
let userAgent = this.props.userAgent;
return (
<MuiThemeProvider muiTheme={getMuiTheme({userAgent, ...muiTheme})}>
<NextPage {...this.props} />
</MuiThemeProvider>
);
}
}
return outputComponent;
}
and to use it with any page:
/pages/index.js
import React, {Component} from 'react'
import withMui from 'components/hocs/withMui';
import RaisedButton from 'material-ui/RaisedButton'
import Dialog from 'material-ui/Dialog'
import FlatButton from 'material-ui/FlatButton'
const styles = {
container: {
textAlign: 'center',
paddingTop: 200
}
}
class Index extends Component {
static getInitialProps ({ req }) {
}
constructor (props, context) {
super(props, context)
this.state = {
open: false
}
}
handleRequestClose = () => {
this.setState({
open: false
})
}
handleTouchTap = () => {
this.setState({
open: true
})
}
render () {
const standardActions = (
<FlatButton
label='Ok'
primary={Boolean(true)}
onClick={this.handleRequestClose}
/>
)
return (
<div style={styles.container}>
<Dialog
open={this.state.open}
title='Super Secret Password'
actions={standardActions}
onRequestClose={this.handleRequestClose}
>
1-2-3-4-5
</Dialog>
<h1>Material-UI</h1>
<h2>example project</h2>
<RaisedButton
label='Super Secret Password'
secondary
onClick={this.handleTouchTap}
/>
</div>
)
}
}
export default withMui(Index); //<--- just wrap the page
i have created github repository for you can use nextjs with material UI
https://github.com/hadnazzar/nextjs-materialui
Tutorial for adding material ui packages and how to create example components are described here
If you are experiencing flickering issues with older versions of material UI i suggest you to look at this article
yarn add #material-ui/core
_document.js
/* eslint-disable react/jsx-filename-extension */
import React from 'react';
import Document, {
Html, Main, NextScript,
} from 'next/document';
import { ServerStyleSheets } from '#material-ui/core/styles';
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Render app and page and get the context of the page with collected side effects.
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()],
};
};
_app.js
import React from 'react';
import PropTypes from 'prop-types';
import Head from 'next/head';
import { ThemeProvider } from '#material-ui/core/styles';
import CssBaseline from '#material-ui/core/CssBaseline';
import theme from '../src/theme';
export default function MyApp(props) {
const { Component, pageProps } = props;
React.useEffect(() => {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<React.Fragment>
<Head>
<title>My page</title>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</React.Fragment>
);
}
MyApp.propTypes = {
Component: PropTypes.elementType.isRequired,
pageProps: PropTypes.object.isRequired,
};

Resources