State.source.tag doesent contains all tags - How to create a tag-cloud?

I try to create a tag cloud. I’ve seen in the twentytwenty-theme and in other themes, that you use a function like this, to get tags:

 const allTags = state.source.tag;
 const tags = post.tags && post.tags.map(tagId => allTags[tagId]);

I tried to implement something similar, but it did not wanted to work.
My WP site has 785 Tags at the moment. If I type console.log(Object.keys(allTags).length); then I get a number like 182. This number is growing as I visit different pages.
If I try: state.source.tag[someID], than it’s working for tags, appearing on my frontpage, but if I write a TagID, what is not related to posts on my frontpage, then I get undefined.
So how could I create a TagCloud?

You are having that problem because some tags you are looking for aren’t actually in the state. The way the fetches are made on twentytwenty-theme and mars-theme, and in your theme probably too, tags are not fetched directly unless you go directly to a tag url like mars.frontity.org/tag/tag-slug.

However, we use the embedded field of the posts to populate the state with categories, tags or author of that posts. I mean, if you fetch https://mysite.com/wp-json/wp/v2/posts?_embed=true (as you do for your homepage for example), you can find information of the author, categories or tags inside the _embedded field, and Frontity uses that information to populate the state with these categories or tags. This is the reason why the tags appearing in your homepage are working.

If you want to get information about a specific tag that is not in the state, you have to make sure to do the fetch first:

// 1. fetch data related to the tag you need
  actions.source.fetch("/tag/tag-slug/");

  // 2. get data from frontity state
  const data = state.source.get("/tag/tag-slug/");

We use this inside a posts because, as I explained before, all the tags of a post are already in the state.

If you really need to populate the state with all the tags of your web, you’d need to fetch them from the https://mysite.com/wp-json/wp/v2/tags endpoint, having in mind that WP REST API is restricted to 100 items per page, so you’ll need to do 8 different fetches at least if you have 785.

Maybe you could try to think of a different solution, adding pagination, just showing some of them or fetching the tag once they click on a specific link. I don’t know what’s exactly your case.

In addition to what Mario said, if you want to get absolutely all the tags for one route I’d do something like this in the handler that will need the data of all the tags:

const response = await libraries.source.api.get({
  endpoint: "tags",
  params: {
    per_page: 100
  }
});

const pages = libraries.source.getTotalPages(response);

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

const requests = [];

for (let page = 2; page <= pages; page++) {
  requests.push(
    libraries.source.api.get({
      enpoint: "tags",
      params: {
        per_page: 100,
        page
      }
    })
  );
}

const responses = await Promise.all(requests);

await Promise.all(responses.map(response =>
  libraries.source.populate({ state, response })
));

The code is not tested, not sure if I messed up something there, but hopefully you get the idea.

1 Like

Thanks @SantosGuillamot and @orballo! No, I definitely don’t need all the tags. I have an ACF Option Page, from where I fetch the tags.
My Tags have an ACF Field (Taxonomy Color), so they should appear in that color. My problem is that I can not reach this ACF Field.
Here is my code so far:

const Tag = connect(({ actions, state, slug, name }) => {
  React.useEffect(() => {
    actions.source.fetch("/tag/" + slug);  
  }, []);
  const data = state.source.get("/tag/" + slug);
  //I hoped that the data after fetch and get will contain the ACF field,
  //but it contains the posts (I guess that surprise only me... :P
  if (data.isReady) {
    return <Link link={"/tag/" + slug}>{name}</Link>;
  }
  return null;
});

const TagCloud = ({ state, libraries, item }) => {
  const options = state.source.get("acf-options-page");
  return (
    <Box>
      {options.acf.tag_cloud.map(({ slug, term_id, name }) => {
        return <Tag key={term_id} slug={slug} name={name} />;
      })}
    </Box>
  );
};
export default connect(TagCloud);

So everything is working, exept that I can not get that ACF field.
My rest api contains it, e.g.: https://wp.szinhaz.online/wp-json/wp/v2/tags/31 -> acf -> tax_color
I could also use it by Tags fetched for Posts, where my Theme use this helper:

export function getPostTags(state, post) {
  const allTags = state.source.tag;
  const tags = post.tags && post.tags.map(tagId => allTags[tagId]);
  return tags ? tags.filter(Boolean) : [];
}

Thats why I tried to use allTags too…

Once you use source.get you get the minimal info about the tag and from there you can navigate through Frontity state. This means that when you do this:

const data = state.source.get("/tag/" + slug);

you can find some info, including the id of the tag. With this id you can access state.source.tag[id] and you’d find there the whole REST API response, where the ACF fields are included. This way, you should be able to get the color.

Thanks @SantosGuillamot, an importent piece of the Frontity-puzzle clicked into it’s place in my mind.

There is still one thing, I dont understand: How could I fetch all the tags at once? Now I fetch them in the Tag component, one-by-one as you can see in my previous comment. I tried something like:

 React.useEffect(() => {
    actions.source.fetch("/tag/" + slugs.join());
  }, []);

in my TagCloud component, but it seems not working that way. Thanks for your patience!

I’m glad you made it work :slightly_smiling_face:

In order to get all the specific tags you should do a fetch similar to https://mysite.com/wp-json/wp/v2/tags?slug=slug-1,slug-2,slug-3..., so in your case you could try fetching this directly this way, maybe in a handler. If you want to fetch all the tags, not a selection of them, you could fetch the tags as Orballo suggested here.

Thanks again @SantosGuillamot I modified my ACF Option page handler, to instantly get the ACF fields of my Tags, like this:

const acfOptionsHandler = {
  pattern: "acf-options-page",
  func: async ({ route, state, libraries }) => {
    // 1. Get ACF option page from REST API.
    const response = await libraries.source.api.get({
      endpoint: `/acf/v3/options/options`,
      head_tags: false
    });
    let option = await response.json();
    const data = state.source.get(route);

    //Get the Tags included in the option page Tag cloud
    const tagIds = option.acf.tag_cloud.map(tag => tag.term_id);
    const response2 = await libraries.source.api.get({
      endpoint: "tags",
      params: {
        include: tagIds,
        head_tags: false
      }
    });
    //Add the values to original tag entities
    const tags = await response2.json();
    option.acf.tag_cloud.map(tag => {
      const obj = tags.find(obj => obj.id == tag.term_id);
      Object.assign(tag, obj);
    });
    Object.assign(data, { ...option, isAcfOptionsPage: true });
  }
};

Now I don’t need to make any other fetch, just use it like:

const TagCloud = ({ state }) => {
  const options = state.source.get("acf-options-page");
  return (
    <Box>
      {options.acf.tag_cloud.map(({ slug, term_id, name, acf }) => {
        const color = acf.tax_color ? acf.tax_color : "#eda407";
        return <Tag key={term_id} slug={slug} name={name} color={color} />;
      })}
    </Box>
  );
};

Thanks again all the help!

3 Likes