Search code examples
materializenext.js

Unable to implement Materialize-CSS initialization scripts with NextJS


I'm using Materialize CSS to style a web app I'm working on. Am building the app using NextJS with ReactJS. I have already styled the navbar using the classes defined in the Materialize CSS file. However, I'm unable to implement the responsive sidebar functionality using the prescribed initialization script as instructed on the Materialize website.

My Navbar component (./components/Navbar.js) looks like this:

import React, { Component, Fragment } from 'react';
import Link from 'next/link';
import $ from 'jquery';

class Navbar extends Component {
  constructor(props) {
    super(props)
    this.props = props;
  }

  componentDidMount = () => {
    document.addEventListener('DOMContentLoaded', function() {
      var elems = document.querySelectorAll('.sidenav');
      var instances = M.Sidenav.init(elems, options);
    });
  }

  render() {
    return (

      <Fragment>
        <nav>
          <div className="nav-wrapper blue">
            <Link prefetch href='/'>
              <a href="/" className="brand-logo">Project Coco</a>
            </Link>
            <a href="#" data-target="slide-out" className="sidenav-trigger"><i className="material-icons">menu</i></a>
            <ul id="nav-mobile" className="right hide-on-med-and-down">
              <li>
                <Link prefetch href='/'>
                  <a>Home</a>
                </Link>
              </li>
              <li>
                <Link prefetch href='/about'>
                  <a>About</a>
                </Link>
              </li>
              <li>
              </li>
            </ul>
          </div>
        </nav>
        {/* Sidenav markup */}
        <ul className="sidenav" id="slide-out">
          <li>
            <Link prefetch href='/'>
              <a>Home</a>
            </Link>
          </li>
          <li>
            <Link prefetch href='/about'>
              <a>About</a>
            </Link>
          </li>
        </ul>
      </Fragment>
    );
  }
}

export default Navbar;

As is obvious, this functionality (sidebar) needs JQuery. So I already have it added via yarn and am invoking it using import $ from 'jquery'. But upon running, it throws an error saying sidenav is not a function.

I even tried entirely doing away with JQuery and just going for the vanilla JS version of the initialization script in componentDidMount:

document.addEventListener('DOMContentLoaded', function() {
    var elems = document.querySelectorAll('.sidenav');
    var instances = M.Sidenav.init(elems, options);
  });

But this time, although no error, the functionality still refuses to work and clicking on the hamburger menu doesn't trigger the sidenav, which it should.

The entire code repo is up on Github for reference and the prototype app is live at schandillia.com. Any solutions?

P.S.: It seems the problem is that the initialization code in ComponentDidMount gets executed before MaterializeCSS.js (being called as an external file), on which it depends. Any work around this could be a potential solution, although that's just an assumption.


Solution

  • You need to import materialize-css into your component. Sadly, if you try to do import 'materialize-css' you'll get a window is undefined error. This is because Next.js is universal, which means it executes code first server-side where window doesn't exist. The workaround I used for this problem is the following:

    Install materialize-css if you haven't:

    $ npm i materialize-css
    

    Import materialize-css into your component only if window is defined:

    if (typeof window !== 'undefined') {
        window.$ = $;
        window.jQuery = $;
        require('materialize-css');
    }
    

    Then, in your componentDidMount method:

    componentDidMount = () => {
        $('.sidenav').sidenav();
    }
    

    So your component looks like this:

    import React, { Component, Fragment } from 'react';
    import Link from 'next/link';
    import $ from 'jquery';
    
    if (typeof window !== 'undefined') {
        window.$ = $;
        window.jQuery = $;
        require('materialize-css');
    }
    
    class Navbar extends Component {
        constructor(props) {
            super(props)
            this.props = props;
        }
    
        componentDidMount = () => {
            $('.sidenav').sidenav();
        }
    
        render() {
            return (
                  <Fragment>
                    <nav>
                        ...
                    </nav>
                    {/* Sidenav markup */}
                    <ul className="sidenav" id="slide-out">
                        ...
                    </ul>
                  </Fragment>
            );
        }
    }
    
    export default Navbar;
    

    Sources: Next.js FAQ and this issue.