4

This is my first project using GatsbyJS and Contentful. Right now I have several posts on the site with different Content Models. For the sake of simplicity let's say I have some Content types that are Photos and others that are Video Embeds. I am using GraphQL to fetch the posts...

I have different components for each type.

Photos (also called the PhotoSection)

const data = useStaticQuery(graphql`
    query {
      allContentfulImage(sort: { fields: date, order: DESC }) {
        edges {
          node {
            image {
              description
              file {
                url
              }
            }
          }
        }
      }
    }
  `)

Video Embeds (also called the Film Section)

const data = useStaticQuery(graphql`
    query {
      allContentfulVideoEmbeds(sort: { fields: date, order: DESC }) {
        edges {
          node {
            embedURL
          }
        }
      }
    }
  `)

I then compile all of the components in another component called Blog

const Blog = () => {
  return (
    <div>
      <AudioSection />
      <FilmSection />
      <PhotoSection />
    </div>
  )
}

export default Blog

The end product is that the posts are in descending order by date BUT they are also organized by their section / Content type. If you follow the codeblock the order is AudioSection -> FilmSection -> PhotoSection. I want them to be ordered by Date (latest first) regardless of the Content type.

Hopefully this makes sense. Not really sure what to do here?

Thank you in advance

Edit... this is post attempting what was suggested. i cut out some of the bulkier sections and but left the PhotoComponent as an example

const BlogTwo = () => {
  const data = useStaticQuery(graphql`
    query {
      music: { bla bla bla }
      videos: { bla bla bla }
      images: allContentfulImage(sort: { fields: date, order: DESC }) {
        nodes {
          type: __typename
          image {
            description
            file {
              url
            }
          }
        }
      }
  `)

  const dataForDisplay = [data.music, data.images, data.videos].sort(
    (a, b) => b.date - a.date
  )

  const componentTypeMap = {
    ContentfulMusicAndArt: MusicComponent,
    ContentfulImage: PhotoComponent,
    ContentfulVideoEmbeds: FilmComponent,
  }

  const MusicComponent = () => {
    return (
      <div>
        bla bla bla
      </div>
    )
  }

  const PhotoComponent = () => {
    return (
      <div className={`${blogStyles.blogDiv}`}>
        <div className={`${blogStyles.blogPost} ${blogStyles.image}`}>
          <img
            src={data.images.nodes.image.file.url}
            alt={data.images.nodes.image.description}
          />
        </div>
      </div>
    )
  }

  const FilmComponent = () => {
    return (
      <div>
        bla bla bla
      </div>
    )
  }

  return (
    <div>
      {dataForDisplay.map(({ type, props }) =>
        React.createElement(componentTypeMap[type], props)
      )}
    </div>
  )
}
export default BlogTwo

1 Answers1

4

Instead of wrapping each content type in a WhateverSection component, combine all three arrays of content into a single array, sort it, and then loop through the combined array rendering the relevant component for each entry.

To make this more sensible, I'm going to refactor your use of static queries by hoisting them up into the blog component, combining them, and adding GraphQL aliases to make retrieving the data less verbose.

const Blog = () => {
  const data = useStaticQuery(graphql`
    query {
      images: allContentfulImage(sort: { fields: date, order: DESC }) {
        nodes {
          type: __typename
          date
          image {
            description
            file {
              url
            }
          }
        }
      }

      videos: allContentfulVideoEmbeds(sort: { fields: date, order: DESC }) {
        nodes {
          type: __typename
          date
          embedURL
        }
      }
    }
  `)

  const dataForDisplay = [...data.images.nodes, ...data.videos.nodes].sort((a, b) => b.date - a.date)

  return (
    <div>
      {dataForDisplay.map(({ type, ...props }) => 
        React.createElement(componentTypeMap[type], props)
      )}
    </div>
  )
}

export default Blog

const componentTypeMap = {
  ContentfulImage: PhotoComponent,
  ContentfulVideoEmbed: FilmComponent,
}
coreyward
  • 77,547
  • 20
  • 137
  • 166
  • Thanks a bunch, I think I'm just about there. However, I'm receiving an error from the console saying -> Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports. Any ideas? – Aaron Lyons Nov 18 '20 at 07:04
  • You probably have a component type in your array that isn't in the `componentTypeMap`. Usually I'll do a quick check and log a warning to the console if this is the case so that I see it in development but don't break a page in production. – coreyward Nov 18 '20 at 15:47
  • i edited my initial post to show what i added (cut out most of the bulk but left the Photo Component as an example). i thought the array was implemented correctly b/c i checked the console and it shows that components are all there – Aaron Lyons Nov 18 '20 at 21:05
  • @AaronLyons I think the issue is that I forgot to expand `nodes` when creating the array of merged types (`dataForDisplay`). I've updated my code to reflect this. – coreyward Nov 19 '20 at 14:51
  • hahaha wow i didn't realize you wanted me to actually include ellipsis in the array. i thought that was just your way of saying etc. just learned what the spread operator is. thanks a bunch dude :) – Aaron Lyons Nov 24 '20 at 04:47
  • Hahaha that's a classic. If this is all buttoned up be sure to accept the answer. :) – coreyward Nov 24 '20 at 14:48