5

I am developing a multi-page website using Next.js for the frontend and Strapi for the backend. On the blog page, the data is dynamically fetched from the database. I am trying to display this data in an adaptive layout using Masonry.

The problem I am having is that on first load, the page displays every grid-item in a single vertical column. On reload, the Masonry layout then takes effect, but with some overlapping items. On a second reload, everything then displays properly and is even responsive. However, if I navigate away from the page and come back, it is back to square one.

Here is the code for the blog page :

import React from 'react';
import Layout from '../components/StaticLayout';
import Link from 'next/link';
import fetch from 'isomorphic-unfetch';
import '../../static/styles.min.css';

let Masonry = '';

export default class Blog extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      appIsMounted: false
    }
  }

  static async getInitialProps() {
    const res = await fetch('http://localhost:1337/blogposts');
    const data = await res.json();

    return {
      posts: data
    };
  }

  async componentDidMount() {
    let masonry = await import('masonry-layout');
    this.setState({
      appIsMounted: true
    })
  }

  render() {
    return (
      <Layout>
        <h1 className="blogHeader">Blog</h1>
        {this.state.appIsMounted ? (
          <div className="grid js-masonry" data-masonry-options='{ "itemSelector": ".grid-item", "columnWidth": 400 }'>
            {this.props.posts.map(
              post => (
                <div key={post.id} className="grid-item">
                  <Link href="/p/[id]" as={`/p/${post.id}`}>
                    <img src={`http://localhost:1337${post.Image.url}`} alt={post.Image.name} className="postImage" />
                  </Link>
                  <div className="text">
                    <Link href="/p/[id]" as={`/p/${post.id}`}>
                      <a className="postLink">{post.Title}</a>
                    </Link>
                    <p className="blurb">{post.Content.substring(0, 300) + '...'}</p>
                    <Link href="/p/[id]" as={`/p/${post.id}`}>
                      <button className="viewPost">Read More!</button>
                    </Link>
                  </div>
                </div>
              )
            )}
          </div>
        ) : null}
      </Layout>
    )
  }
}

To be noted, the StaticLayout script only ensure the header is at the top of each page.

EDIT

So I've solved the overlapping part of the problem. Instead of initializing Masonry in the html, I've added a js file to './static'. On my blog page, I've added the following :

import Head from 'next/head'

...

return(
  <Head>
    <script type="text/javascript" src="/static/runMasonry.js"></script>
  </Head>
  ...
)

And the runMasonry.js file looks like this :

window.onload = function () {
  var elem = document.querySelector('.grid');
  var msnry = new Masonry(elem, {
    // options
    itemSelector: '.grid-item',
    columnWidth: 160,
    gutter: 20
  });
}

But when I first arrive on the page or navigate away and come back, the elements are still displayed in a column and require a reload.

Phil
  • 383
  • 1
  • 4
  • 18
  • Sounds like css loading issue, do u have the same issue on production build? – felixmosh Sep 05 '19 at 14:53
  • @felixmosh Yes, it is the same problem after running next build and next start – Phil Sep 05 '19 at 14:53
  • Do you use next-css? Do you see any network errors related to css? – felixmosh Sep 05 '19 at 14:56
  • @felixmosh Yes, I am using next-css to load a minified css file into the blog page. And my page has no network errors. All responses are 200 with a couple of 302 – Phil Sep 05 '19 at 15:02

1 Answers1

2

Came up with a non-ideal fix, but it works.

Added the following code to the runMasonry.js file :

window.addEventListener("DOMSubtreeModified", function () {
    var elem = document.querySelector('.grid');
    if (elem) {
        var msnry = new Masonry(elem, {
            // options
            itemSelector: '.grid-item',
            columnWidth: 160,
            gutter: 20
        });
    }
});

So it detects when new page content is loaded and, if it finds my .grid element, re-runs masonry.

Leaving this open in case someone can provide a better, long-term solution.

Phil
  • 383
  • 1
  • 4
  • 18