Reputation: 744
I have a problem with inconsistent behavior between the site generated by gatsby develop
and gatsby build
. The result is a site that works in development but not production.
A simple blog-like site (personal profiles instead of blog posts). The index page is a list of people, and each item in that list links to that person's profile page.
I'm using Gatsby to build the site. My data (the personal profiles) are entries hosted on the Contentful headless CMS. I'm using the gatsby-source-contentful source plugin.
I cannot shuffle the order of the profile list items on the index page. The only behavior where my site goes beyond any basic gatsby tutorial is that I want to randomize the list of profiles on my index page (to give everyone a fair chance at being listed at the top).
gatsby build
generates a static index page with the list in one permutation.
When loaded in a browser the ThumbList
component re-shuffles those items to another permutation on render and some sub-elements are not properly managed by react and stay stuck as other elements shift. This leads, for example, to profile images paired with the wrong name.
The following code is somewhat summarized for readability.
src/pages/index.js:
import React from "react"
import Layout from "../components/layout"
import ThumbList from "../components/thumbList"
import { graphql } from "gatsby"
export default ({data}) => {
// people are called "creators" in the app
const creatorData = data.allContentfulCreator.edges
const shuffledData = shuffle(creatorData.slice(0))
return (
<Layout>
<ThumbList data={shuffledData} />
</Layout>
)
}
const shuffle = (a) => {
// Fisher-Yates randomized array in-place shuffle algo
// ...
return a
}
export const query = graphql`
{
allContentfulCreator {
edges {
node {
id
slug
name
bio {
id
bio
}
mainImage {
file {
url
}
}
}
}
}
}
`
src/components/thumbList.js:
import React from "react"
import { Link } from "gatsby"
// A list of creator profile links, with name and picture thumbnail
export default ({data}) => {
return (
<div>
<ul>
{
data.map(({node}) => {
const creator = node
const link = "/" + creator.slug
const image = "https:" + creator.mainImage.file.url
return (
<li key={creator.id}>
<Link to={link}>
{creator.name}
</Link>
<img src={image} />
</li>
)
})
}
</ul>
</div>
)
}
The result of gatsby build
is an index.html
containing:
<ul>
<li>
<a href="/alice">
Alice
</a>
<img src="cdn.com/alice.jpg">
</li>
<li>
<a href="/bob">
Bob
</a>
<img src="cdn.com/bob.jpg">
</li>
<li>
<a href="/eve">
Eve
</a>
<img src="cdn.com/eve.jpg">
</li>
</ul>
However, when viewing the index page in the browser (via gatsby serve
or a deployed version of the site) the live react ThumbList
component again shuffles the data in its render method.
The result re-rendered html:
<ul>
<li>
<a href="/alice">
Bob
</a>
<img src="cdn.com/alice.jpg">
</li>
<li>
<a href="/bob">
Eve
</a>
<img src="cdn.com/bob.jpg">
</li>
<li>
<a href="/eve">
Alice
</a>
<img src="cdn.com/eve.jpg">
</li>
</ul>
Here only the text nodes are rearranged to match the new order (confirmed by console logging the array order), but the links and image elements remain stuck where they were in the static build. Now the names, images, and links are scrambled.
Two other things to note:
gatsby develop
. I guess it's because in development index.html is generated without its static content in the body - allowing react complete control over the DOM from the start with no
static scaffolding to confuse it.(very abbreviated for readability)
<ul>
<li key="165e2405">
<GatsbyLink to="/bob">
Bob
</GatsbyLink>
<img src="cdn.com/bob.jpg"></img>
</li>
<li key="067f9afc">
<GatsbyLink to="/eve">
Eve
</GatsbyLink>
<img src="cdn.com/eve.jpg"></img>
</li>
<li key="ca4b82bf">
<GatsbyLink to="/alice">
Alice
</GatsbyLink>
<img src="cdn.com/alice.jpg"></img>
</li>
</ul>
/static/d/556/path---index-6a9-L7r5Sntxcv3RUIoHYIR3Qqm9Jmg.json
), but
then I want to dynamically shuffle that data and rearrange the DOM on render. Is that not possible with Gatsby? Do I need to give up the pre-fetched data during build and just consider that a dynamic component and fetch the
data via the Contentful API in componentDidMount
?Upvotes: 1
Views: 939
Reputation: 11
I've been struggling with this too recently!
My solution is to render the shuffled content once the parent component mounts using ReactDOM's render method:
import React, { useRef, useEffect } from "react";
import { render } from "react-dom";
import shuffle from "../utils/shuffle";
const shuffledArray = shuffle(array.slice());
// The below should still be able to work with graphql fetched data
// as I think the array will be saved to a variable for use in the client,
// although in my case I haven't used it so can't be fully sure
const ShuffledJSXElements = () =>
shuffledArray.map(creator => (
<li key={creator.id}>
<Link to={link}>
{creator.name}
</Link>
<img src={image} />
</li>
));
const Page = () => {
const shuffledContentContainerRef = useRef();
useEffect(() => {
const ontainer = portfolioContainerRef.current;
render(<ShuffledJSXElements />, container);
}, []);
return (
<MainWrapper>
<StyledPortfolioGridWrapper ref={shuffledContentContainerRef} />
</MainWrapper>
);
};
export default Portfolio;
One frustrating thing about this is that the container element won't have an awareness of its own height before the content is rendered, so the layout might jump about a bit. A workaround for this is to use a min-height
css property.
Upvotes: 1