Retrieve all posts of CPT without knowing slugs

OK, I thought this was simple, but apparently I can’t manage it myself and after looking in the documentation and forums for a while I still can’t figure it out, so I’m asking for help. I’m probably totally missing one step in the process.

What I want is to create a Team component that loops through all posts of the custom post type called ‘team’, but currently I can’t manage to populate Frontity with the posts of that CPT.

The enpoint for the CPT is:
https://frontity.noesteprojecten.nl/wp-json/wp/v2/team

So far I’ve added the CPT to frontity.settings.js:

...
postTypes: [
  ...
  {
    type: "team",
    endpoint: "team",
    archive: "/team-archive",
  },
]
...

Added the CPT to my beforeSSR (is this even right for this purpose?):

beforeSSR: async ({ state, actions }) => {
  await Promise.all(
    [
      actions.source.fetch("/team"),
    ]
  );
},

And then started working on a component Team:

import React, {useEffect} from "react";
import { connect, styled } from "frontity";

const Team = ({ state, actions, libraries }) => {

   useEffect(() => {
      // No need to use `async/await` here
      actions.source.fetch('/team')
   }, []);

   // The data will not exist at first, `dataPost.isReady` will be false.
   // But then, it will rerender when `actions.source.fetch` is finished.
   const dataPost = state.source.get("/team");

   // This will work just fine.
   dataPost.isReady ? console.log(dataPost, state.source.team) : console.log('nope...');

   return null;
}

export default connect(Team);

However, dataPost returns an error: post type from endpoints \"posts,pages,media\" with slug \"team\" not found. But when I visit the archive page for team, the post type does exists: https://frontity-test-ten.now.sh/team-archive

After visiting the CPT archive page, the posts are available for the Team component. But of course I need to have them available on the front page already. So I think I will still need to populate Frontity with the posts from the CPT, but what is the correct procedure for this?

This is a link to my repo:
https://bitbucket.org/noesteijver/frontity/src/master/

I think I solved part of the problem by writing a handler, adding it to the handlers and using it in the beforeSSR. Is this really needed when you want to use posts outside of the archive loop and single page?

const teamHandler = {
  name: 'team',
  priority: 20,
  pattern: 'team',

  func: async ({ route, params, state, libraries }) => {
    // 1. get products data
    const response = await libraries.source.api.get({
      endpoint: 'team',
      params: {per_page: 100},
    });

    const entitiesAdded = await libraries.source.populate({ response, state });

    Object.assign(state.source.data[route], {
      isTeam: true,
      isPostType: true,
      items: entitiesAdded.map(item => ({
        type: item.type,
        id: item.id,
        link: item.link
      }))
    });
  }
}

...

beforeSSR: async ({ state, actions }) => {
  // getNameAndDescription,
  await Promise.all(
    [
...
      Object.values(teamHandler).map(() =>
        actions.source.fetch("team"),
      ),
...
    ]
  );
},
...
source: {
  handlers: [teamHandler],
}

Now I could see that state.source.team was populated, but odd enough I still had lots of trouble to use the data, but after loads of trial & error I managed to, but I’m unsure if this is the proper way of doing it. My team.js looks like this now.

import React, {useEffect} from "react";
import { connect, styled } from "frontity";
import Member from "./member";
import Item from "./list/list-item";

const Team = ({ state, actions, libraries }) => {
   useEffect(() => {
      actions.source.fetch("team");
    }, []);

    const data = state.source.get("team");

   if (data.isReady) {
      const members = data.items.map(({ type, id }) => state.source[type][id]);

      return (
         <>
            <h1>Team</h1>
            <MemberContainer>
               {data.items.map(({ type, id }, index) => {
               const item = state.source[type][id];
                  return <Member item={item} />;
               })}
            </MemberContainer>
         </>
      );
   }

   return null;
}

export default connect(Team);

const MemberContainer = styled.div`
...
`;

And finally, the member component looks like this:

import React, {useEffect} from "react";
import { connect, styled } from "frontity";
import FeaturedMedia from "./featured-media";
import Title from "./title"

const Member = ({ state, item, count }) => {
   return (
      <MemberItem href={item.link}>
         <FeaturedMedia id={item.featured_media} />
         <h2 dangerouslySetInnerHTML={{ __html: item.title.rendered }} />
         <div dangerouslySetInnerHTML={{ __html: item.excerpt.rendered }} />
      </MemberItem>
   );

   return null;
}

export default connect(Member);

const MemberItem = styled.a`
...
`;

My biggest problem at the moment is that the FeaturedMedia component won’t work. If I visit the specific page of a member and return to the front-page, the image is there. If I visit the page of another member and return to the front-page, the image is there, but the other image is gone. I looked how the component is used in list-item.js of the Mars theme, but I’m not seeing any specifics that I’m missing. So I’m unsure why the image is gone.

I’m really at a loss. If I’ve got the id of the featured-image in item.featured_media, this should just be working, am I right?

<FeaturedMedia id={item.featured_media} />

Or am I missing a crucial step in the process of fetching the attachment? I was under the impression that the FeaturedMedia component was always working when feeded an id, but maybe I’m wrong.

I cracked it, I guess.

I had to use the location of the archive in the fetch function. I configured the post-type to have team-archive as archive location, but when I changed this to team, things started to work…

   useEffect(() => {
      actions.source.fetch("/team/");
   });

   const data = state.source.get("/team/");

Is this assumption correct?

Hey @dominique, I just saw that this thread was marked as solved a long time ago but I want to clarify some things about custom post types in Frontity.

So, you added the following settings for your @frontity/wp-source package:

...
postTypes: [
  ...
  {
    type: "team",
    endpoint: "team",
    archive: "/team-archive",
  },
]
...

This configurations means the following assertions are true in your WordPress site:

  • team is the type of the custom post type (the value that appears in the type field of these entities).
  • team is the endpoint where these entities are fetched from the WordPress REST API (i.e. /wp-json/wp/v2/team)
  • /team-archive is the link in your site where posts of type team are listed

When you use actions.source.fetch() and state.source.get() you should use the link, nor the type neither the endpoint. Just doing this in your Team component should work as expected, without having to create a new handler.

useEffect(() => {
  actions.source.fetch("/team-archive/");
});

const data = state.source.get("/team-archive/");

If you want to use a link like /team/ instead, you would have to change your CPT configuration in WordPress and set the archive field in the settings to that value, also in Frontity. Then, you could use "/team/" when calling actions.source.fetch() and state.source.get().

I hope this helps to clarify your doubts.

Cheers!

4 Likes

Hi David,

I missed the nuance that Frontity will receive posts from a specific ‘link’ where posts are listed. I thought the REST-endpoint was used for this. Thanks for the further explanation!

2 Likes

Hello,

Thanks for a bunch of knowledge! I’m trying to do something similar and I’ve got a problem with single page - it’s always 404 (in WP these links work). List page is working fine.

Does above example support single team post page?