Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context does not apply through <Main /> #873

Closed
Ehesp opened this issue Jan 24, 2017 · 13 comments
Closed

Context does not apply through <Main /> #873

Ehesp opened this issue Jan 24, 2017 · 13 comments

Comments

@Ehesp
Copy link
Contributor

Ehesp commented Jan 24, 2017

Hello,

I'm currently using material-ui in my app. I'm taking advantage of pages/_document.js, along with <Main />:

// pages/_document.js

render() {
    return (
 <html>
        <Head>
        </Head>
        <body>
            <MuiThemeProvider muiTheme={{ ... ]}>
              <Main />
            </MuiThemeProvider>
          <NextScript />
        </body>
      </html>
    );
}

Then the page being loaded:

// pages/index.js
render() {
    console.log(this.context.muiTheme) // undefined
    return (...);
}
...
Index.contextTypes = {
    muiTheme: React.PropTypes.object.isRequired
};

So normally, the context would be passed through and I'd have access to the theme properties, and so would any material-ui components. However, in this case the context is not being applied.

Logging out the context in the Main source code works.

  1. Is this expected?
  2. Is there any work around?
@Ehesp Ehesp changed the title Context does not apply to <Main /> Context does not apply through <Main /> Jan 24, 2017
@arunoda
Copy link
Contributor

arunoda commented Jan 24, 2017

@Ehesp can I have a sample repo. So, I could work on this pretty fast.

@Ehesp
Copy link
Contributor Author

Ehesp commented Jan 24, 2017 via email

@Ehesp
Copy link
Contributor Author

Ehesp commented Jan 24, 2017

@arunoda Here you go: https://github.com/Ehesp/next-mui-context

Install modules and just npm run dev. So basically the _default.js file wraps the entire application in MuiThemeProvider. Under the hood this just provides context to the app. The MUI component within index.js (<RaisedButton ...>) then uses this context to apply the theme. Currently it errors with TypeError: Cannot read property 'prepareStyles' of undefined, which means there's no context (mui/material-ui#5330 (comment)).

In Index, if you comment out render and uncomment the currently commented ones, it works.

@arunoda
Copy link
Contributor

arunoda commented Jan 25, 2017

@Ehesp hey, I checked it and unfortunately that's something you can't do.
The idea behind the _document.js is to customize the base HTML document of the app.
Although it uses React, it's something only runs on the server. So, React is just an API to customize the base HTML page.

Actual, Next.js app is runs inside the <Main/>. So, basically that's where the react's root element lives.

So, you need to provide your MUI context in the page's render method.

@arunoda
Copy link
Contributor

arunoda commented Jan 25, 2017

Also there's a problem with the SSR when doing styles. Check the console.
That's because of the userAgent mismatch in the server and client.

Here's a easy and ugly fix: mui/material-ui#3009 (comment)

But here's the proper way to do it:

import React from 'react';
import RaisedButton from 'material-ui/RaisedButton';

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';

export default class extends React.Component {

    static getInitialProps = ({ req }) => {
        const userAgent = req? req.headers['user-agent'] : navigator.userAgent
        return { userAgent }
    }

    render() {
        const { userAgent } = this.props
        return (
            <div>
                <MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme, { userAgent })}>
                    <div>
                        <p>Render a styled MUI Button: </p>
                        <RaisedButton label="Button" primary style={{ margin: 12 }} />
                    </div>
                </MuiThemeProvider>
            </div>
        );
    }
}

@arunoda arunoda closed this as completed Jan 25, 2017
@mistakenelf
Copy link

mistakenelf commented Jan 25, 2017

I use material-ui in my app. I created a layout component that will just wrap whatever is passed into the layout component with the theme provider. Is that what your trying to accomplish?

@Ehesp
Copy link
Contributor Author

Ehesp commented Jan 25, 2017 via email

@arunoda arunoda removed the Type: Bug label Jan 25, 2017
@Ehesp
Copy link
Contributor Author

Ehesp commented Jan 25, 2017

@arunoda #590

I believe this is a similar issue. If wrapping each page item in Provider, which provides store context is rerendered on each page load how is state supposed to be persisted? Each page would have a fresh store.

It's almost as if there needs to be a React wrapper for every page?

@Ehesp
Copy link
Contributor Author

Ehesp commented Jan 25, 2017

For those interested, here's the solution we took, handles both Redux & Material-UI:

// pages/index.js
function Index() {
    ...
}

export default Page(connect()(Index));

Every page is wrapped in a Page decorator/HOC, which does the following:

const decorator = (ComposedComponent) => {
  return class extends Component {

    static async getInitialProps(ctx) {
      const { req } = ctx;
      const isServer = !!req;
      const userAgent = req ? req.headers['user-agent'] : navigator.userAgent;

      // Second param here is initial redux state on the server
      const store = initStore(reducers, {}, isServer);

      let pageProps = {};

      if (ComposedComponent.getInitialProps) {
        pageProps = await ComposedComponent.getInitialProps(ctx);
      }

      return {
        ...pageProps,
        initialState: store.getState(),
        isServer,
        userAgent,
      };
    }

    constructor(props) {
      super(props);
      this.store = initStore(reducers, props.initialState, props.isServer)
    }

    render() {
      return (
        <div>
          <Provider store={this.store}>
            <MuiThemeProvider muiTheme={getMuiTheme({ userAgent: this.props.userAgent })}>
              <ComposedComponent
                {...this.props}
              />
            </MuiThemeProvider>
          </Provider>
        </div>
      )
    }
  };
};

export default decorator;

This now provides Redux & MUI throughout every page. It also allows the actual page file to use getInitialProps as well, using the following lines in the decorator:

      if (ComposedComponent.getInitialProps) {
        pageProps = await ComposedComponent.getInitialProps(ctx);
      }

These are then applied into the props of the contained page/component.

@timneutkens
Copy link
Member

@arunoda should we create an example showing how to use material-ui with next.js? We've had questions about material-ui multiple times 😄

@arunoda
Copy link
Contributor

arunoda commented Jan 25, 2017

@timneutkens why not :)

@maxs15
Copy link

maxs15 commented Mar 20, 2017

Hey @arunoda, really like the library but this is a main issue in my opinion.
It looks like there is currently no way for a component to live globally in the app ?
Would be nice to have a place to declare components outside the pages.

@ryanbas21
Copy link

ryanbas21 commented Jun 10, 2017

A solution that I just wrote and havent fully tested out but seems to be working is

HOC

import { compose } from 'redux';
import withRedux from 'next-redux-wrapper';
import store from '../../store/index';
import { userAgent, withMuiTheme } from './withMUITheme';

// compose higher order Component
export default compose(withMuiTheme(userAgent), withRedux(store));

import React from 'react';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';

export const userAgent = getMuiTheme({ userAgent: false });
export const withMuiTheme = userAgent => Component => () =>
  <div>
    <MuiThemeProvider muiTheme={userAgent}>
      <Component />
    </MuiThemeProvider>
  </div>;

then my page can be exported

import withMUITheme from '../MaterialHOC/index';
const Board = () =>
  <div>
    <TeamMembers />
    <Navbar team={"Ryan's Team"} />
    <TeamStatus />
    <Checkin />
  </div>;

export default withMUITheme(Board);

@lock lock bot locked as resolved and limited conversation to collaborators May 11, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants