How to load (older) posts by post ID

Hello!

On posts, I have a meta field that allows the author to select up to 2 related posts on the WordPress side. On the Frontity theme side, I take those IDs and utilize it to create clickable thumbnails for those posts.

On average it works fine, but when the post is older, or not in the same category, it doesn’t load it properly. I’ve utilized the libraries.source.api and libraries.source.populate to get the post by ID, but I still run into 2 main issues:

  1. the thumbnail doesn’t populate into the source
  2. the title and excerpt are fetched and output fine, but when the link is clicked, the site crashes

Here’s the Post component:

import React, { useEffect, useState } from 'react';
import { connect, styled, Head } from 'frontity';
import SubscribeBox from '../newsletter/SubscribeBox';
import PostFooter from './PostFooter';
import MoreArticles from './MoreArticles';
import mq from '../MediaQueries';
import Link from "@frontity/components/link";
import ShareLinks from '../snippets/ShareLink';

const Post = ({ state, libraries, actions }) => {
  //Get info about the current URL
  const data = state.source.get(state.router.link);

  //Get the current post's data
  const post = state.source[data.type][data.id];
  
  //get the featured Image ID
  const featuredImgId = post.featured_media;

  //get the featured image from attachments
  const featuredImgData = state.source.attachment[featuredImgId];

  //the ability to hide the featured image (binary)
  const hideFeaturedImage = post.meta_box.hide_post_featured_image;
  
  //get the author data
  const author = state.source.author[post.author];
  // Get a human readable date.
  const date = new Date(post.date);

  //check if the post is sponsored
  const sponsored = post.meta_box.sponsored;
  // const sponsoredCompany = post.meta_box.company_sponsor;
  const sponsoredURL = post.meta_box.sponsored_url;

  //get category info
  const categoryID = post.categories[0];
  const categoryName = state.source.category[categoryID].name;
  const categoryLink = state.source.category[categoryID].link;


  //get the html2react component that allows us to output html
  const Html2React = libraries.html2react.Component;  

  const catData = state.source.get(categoryLink);

  //get the related posts IDs (up to 2 are selectable)
  const selectedPostIds = post.meta_box.post_relatedPosts;

  const [loadedSelectedPosts, setLoadedSelectedPosts] = useState([]);

 

  /**
   * Once the post has loaded in the DOM, prefetch both the
   * home posts and the category component so if the user visits
   * the home page or the category page, everything is ready and it loads instantly.
   * Also fetch the selectedPosts (if there are any) so people can see those are the bottom
   * of the article
   */
  useEffect(() => {
 
    actions.source.fetch("/");
    actions.source.fetch(categoryLink);

    const loadSelectedPosts = async (selectedPostIds ) => {
    
      for(const selectedId of selectedPostIds) {
        if(loadedSelectedPosts.length < selectedPostIds.length){
        // Fetch the post data from the endpoint
        const response = await libraries.source.api.get({
          endpoint: `/wp/v2/posts/${selectedId}`,
        });

    
        const postsAdded = await libraries.source.populate({ response, state }).then(
          res => {                      
       
            actions.source.fetch(res[0].link);            
   
            setLoadedSelectedPosts(
              [...loadedSelectedPosts, res[0]]
            );

                
          }
                                 
        );         
      }
      }       
    
    }
    loadSelectedPosts(selectedPostIds);
 
   
  }, [loadedSelectedPosts.length] );


  return(
    <>
    { (!post.yoast_head_json.description && post.excerpt.rendered ) &&
    <Head>
      <meta name="description" content={post.excerpt.rendered.replace(/<[^>]*>?/gm, '')} />
    </Head>
    }
    <Article>      
      <div className="container">
        <div className="row">
          <div className="col-lg-11 offset-lg-1">
            <div className="row">
             
              <div className="col-lg-11">
              {categoryName === "Digging In" &&
                <DiggingIn link={categoryLink}>Digging In</DiggingIn>
              }
              { (sponsored  && sponsoredURL ) &&
              <Sponsored link={sponsoredURL} target="_blank">Sponsored</Sponsored>
              }
              <H1>               
                <Html2React html={  post.title.rendered } />                            
              </H1>
              </div>             
              <div className="col-lg-9">
                <LeadParagraph>           
                  <Html2React html={  post.excerpt.rendered } /> 
                  {/* <RevealText text={post.excerpt.rendered} delay="0.2" /> */}
                </LeadParagraph>
                { (featuredImgData && hideFeaturedImage !== "1")  && 
                <FeaturedImg src={ featuredImgData.source_url } alt={featuredImgData.alt_text} />
                }
                {featuredImgData &&
                  <Caption><Html2React html={  featuredImgData.caption.rendered }  /></Caption>
                }
              </div>
              <ArticleBody className="col-lg-8 col-md-10 order-1 order-lg-0">
                <Html2React html={  post.content.rendered }  />
              </ArticleBody>
              <Aside className="col-xl-3 col-lg-4 pl-lg-5 pl-xl-3 offset-xl-1 order-0 order-lg-1">
                <PostInfo>
                  <Link link={author.link}><AuthorImg src={author.avatar_urls['96']} alt={author.name} /></Link>
                  <div>
                   <AuthorLink link={author.link}>by {author.name}</AuthorLink>
                    <DateWrapper>{date.toLocaleDateString('en-ca', { year: 'numeric', month: 'long', day: 'numeric' } )}</DateWrapper>
                    in <Link link={categoryLink}>{categoryName}</Link>
                  </div>
                </PostInfo>                 
                  <StyledSubscribeBox 
                  className="dark-form dark-form--mobile"
                  headerText="Become smarter in just 5 minutes" 
                  header="h3"
                  blurbText="Get the 5 minute weekly newsletter for all the latest in the Canadian construction industry." 
                  buttonText="Subscribe"  
                  formWidth="100%"
                  location="post-sidebar"                      
                  />
              </Aside>
            </div>
            <ShareLinks />              
            <PostFooter />
          </div>
          <MoreArticles when={ catData.isReady && ( loadedSelectedPosts.length ===  selectedPostIds.length )  } />      
        </div>
      </div>
    </Article>
    </>
  );
}

export default connect(Post);

Here’s the MoreArticles component (located inside the Post Component, at the bottom):

import React from 'react';
import { styled, connect } from 'frontity';
import { IoFlashSharp } from 'react-icons/io5';
import ArticleList from './lists/ArticleList';
import ArticleNumberedTextList from './lists/ArticleNumberedTextList';

const MoreArticles = ({ state }) => {

  const data = state.source.get(state.router.link);
  const postId = data.id;

  //Get the current post's data
  const post = state.source[data.type][postId];

  //get the category information
  let categoryID = post.categories[0];
  let categoryName = state.source.category[categoryID].name;
  let categoryLink = state.source.category[categoryID].link;
  let catData = state.source.get(categoryLink);
  
  const catPosts = catData.items;


  //set base of suggested posts as most recent
  let morePosts = [...catPosts];

  //get any hand picked suggested posts
  const selectedPostIds = post.meta_box.post_relatedPosts;

  let selectedPosts = []; 

  if(morePosts && selectedPostIds){
    selectedPostIds.map((selectedPostId) => {
      const selectedPost =  state.source.post[selectedPostId];
      const selectedPostObj = (selectedPost) && {type: 'post', id: selectedPostId, link: selectedPost.link};
      selectedPosts.push( selectedPostObj );
    })
  }


 
  if(morePosts && selectedPosts){ 
    morePosts.unshift(...selectedPosts);
  } 


  let postCount = 0;
  const filteredCatPosts = ( morePosts && post.id ) ? 
    morePosts.filter(post => post.id !== postId ).filter(post => {
        postCount++;
        if(!selectedPostIds.includes(post.id) && postCount > 2){
          return post;
        }else if(postCount <= 2){
          return post;
        }
    })
   
    : [];



  return(
    <>
    { filteredCatPosts && 
      <MoreArticlesContainer className="col-12">
        <div className="row">
          <div className="col-lg-8">
            <h3><IoFlashSharp />You might also like</h3>            
            <ArticleList articles={filteredCatPosts.slice(0, 2)} />
          </div>
          <div className="col-lg-4">
            <H3>More from {categoryName}</H3>
            <ArticleNumberedTextList posts={filteredCatPosts.slice(2, 7)} />
          </div>
        </div>
      </MoreArticlesContainer>
    }
    </>
  );
}

export default connect(MoreArticles);

Really appreciate any help on this - please let me know if you need to see any more info or code or anything. I’m still learning React in general so appreciate any pointers at all - thank you!

Hey @brett,

this is how I usually do this:

  1. Create an action that fetches a custom list of posts:
    fetchPosts:
      ({ state, libraries }) =>
      async (prms, callback = (entities?: any) => {}) => {
        // Get other images
        const response = await libraries.source.api.get({
          endpoint: "post",
          params: prms,
        });

        const entitiesAdded = await libraries.source.populate({
          response,
          state,
        });
        callback(entitiesAdded);
      },
  1. Create a component for your related posts. Something like this:
const RelatedPosts = ({ post }: RelatedPostsProps): JSX.Element => {
  const { state, actions, libraries } = useConnect<Packages>();

  /* Holds a reference via data object of the posts.*/
  const [relatedPosts, setRelatedPosts] = useState<DataEntity[]>([]);

  useEffect(() => {
    if (post.acf.relatedPosts.length > 0) {
      actions.theme.fetchPosts(
         /* Here I assume that post.acf.relatedPosts holds an array of the related posts ids*/
        { include: post.acf.relatedPosts },
        setRelatedPosts
      );
    }
  }, [post]);

  return (
    relatedPosts.length > 0 ? (
      <div>
        <div>
          {relatedPosts.map(({ type, id }) => (
            <PostCard item={state.source[type][id]} key={id} />
          ))}
        </div>
      </div>
    ) : (
      <div>No more posts</div>
    )
  );
};
  1. Enjoy and Profit! :slight_smile:

This is just a qucik this i put together from a few different projects. So you can ignore if you see some strange stuff, most probably i just didnt clean it up well enough. This is the easiest way I have found to do this. Good luck, let me know if you have questions.