Fetch categories by priority/order with condition

Hi all

First - this is really great project!
I’m new here and I hope I’m describing my question clear is possible:

I build two “widget like” components that will display posts from two different categories.
My goal is the fetch category X and only when X is ready (.isReady) fetch category Y

If I understand it right; the best place to this is on the root index.js under actions: using afterCSR/beforeCSR etc…

This is what I did:

actions: {
theme: {
  afterCSR: async ({ state, actions }) => {
    await actions.source.fetch('/category/nba/');
    const dataNBA = state.source.get('/category/nba/');
    if (dataNBA.isReady) {
      actions.source.fetch('/post_format/gallery/');
    }
  }, ... 

I’m not sure if this is the right solution and have the best performance.
Please help me to understand if I’m doing it right…

Thanks

Hi @shani!

Welcome to the Community!

I don’t understand well why do you need to make the fetch to '/post_format/gallery/' when dataNBA.isReady. As frontity access the REST API, data from both endpoints should be available anytime

Maybe the names of the methods are confusing you. These methods belong to the wp-source package that is documented in the docs

https://docs.frontity.org/api-reference-1/wordpress-source

Let me point out the purpose of these methods and the difference between them

actions.source.fetch()

fetch is an action available though the wp-source package that fetches all entities related to a link. This means, this method will actually perform a request and will put the information retrieved in the state

This method returns a promise, so when the promise is resolved the data will be fully available in the state.

The property isReady available for each link info (that can be get through state.source.get()) informs when the request triggered by actions.source.fetch() has finished and the data is available in the state

state.source.get()

This is the method that allow us to read data available for each link from the state. As this method returns final data (titles, contents, author names…) most of the times it will be used directly from the React component that is going to display that data.


So, if you have created a widget component in React, maybe this logic can be directly part of that component (in the useEffect hook for example) because by using afterCSR all the pages loaded by Frontity (even those that are not using that widget) will execute that logic. If you put the logic in the widget component, the logic (fetching data and so) will be executed only when the component is used

In case you want to learn more about how the fetch action and get method works along with the state manager here you have some videos where the DevRel team talks about this

Hope this helps

Thanks for your answer!

This may a little confusing.
Looking at this docs: https://docs.frontity.org/api-reference-1/wordpress-source#how-to-use (very similar what I need)
I’ve understand that: First I need to Fetch the data from the category and then use state.source.get
Current me if I wrong …
I’m using afterCSR because I was unable to create a dynamic component that loads my data by order.

Lets say I have created this component:

const Widget1 = ({ categorySlug, state, actions }) => {

   useEffect(() => {
      actions.source.fetch(categorySlug);
   }, []);

   const data = state.source.get(categorySlug);
   if (data.isReady) {
     // ... do stuff
   }

In my HP I calling:

<Widget1 categorySlug="/category/nba/" />
<Widget1 categorySlug="/category/boxing/" />
// etc..

How I can tell “boxing” to start loads after nba ?

Hi @shani,

One key idea in Frontity is that the source of data is the state. And React components that are connected to the state, can “react” (they will be re-rendered with latest data) to updates in that state.

All the logic regarding data is handled by the wp-source package.

In the docs you can find the following example with comments that I think it can help you to understand better how this works

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

// In a React component that uses "connect":
const CategoryNature = ({ state, actions }) => {
  // 1. fetch data related to a path
  // With this useEffect we make the call to fetch
  // only the first time the component is rendered.
  // When the data is fetched, the state is updated with the new data
  // so the component is re-rendered and "data" will get proper content

  useEffect(() => {
    actions.source.fetch("/category/nature/");
  }, []);

  // 2. get data from frontity state
  const data = state.source.get("/category/nature/");

  // 3. get entities from frontity state
  if (data.isCategory) {
    // the category entity
    const category = state.source.category[data.id];

    // posts from that category
    const posts = data.items.map(({ type, id }) => state.source[type][id]);

    // 4. render!
    return (
      <>
        <h1>{category.name}</h1>
        {posts.map((p) => (
          <a href={p.link}>{p.title.rendered}</a>
        ))}
      </>
    );
  }

  return null;
};

export default connect(CategoryNature);

The key idea here is that when the actions.source.fetch finishes, the component is re-rendered with updated information, so the state.source.get method will be able to get data form the actions.source.fetch

This approach (using the state as the source of truth) have several advantages:

  • The same data is used across all your React components
  • This state can be used in both SSR and CSR versions of your pages and components
  • Whenever the state is updated with new information all components connected (by using connect HOC) to the state will be re-rendered

I still don’t understand why do you need to load one category after the other as they are independent categories that can be loaded simultaneously, and whenever the data is available in the state, each <Widget1> component will re-render accordingly

Hope this helps

The main reason I want to load categories depend on each other is because sometimes “boxing” category loads/rendering before “nba” (when user reloading the page/first time visit)
This is interpret for my smooth HP widgets load and give the user poor experience …

Imagine you have 3 boxes of <Widget1> one below the other, and the 3th widget loads before the 1st…
I want while user scroller down, the widget (categories) will load by their order

// 1st on to show:
<Widget1 categorySlug="/category/nba/" />
// 2nd on to show:
<Widget1 categorySlug="/category/boxing/" />
// 3rd on the show:
<Widget1 categorySlug="/category/football/" />
// etc...

Hi @shani,

If that case the widget component is not meant to be “independent” so I guess it makes sense the widget is not in charge of making the requests. It can be just a “dumb” component displaying the info it receives

You could do something like this to assure the widgets are only rendered when the information is available

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

const BlockWithWidgets = ({ state, actions }) => {

  // 1. we make all the requests at the same time. 
  useEffect(() => {
    Promise.all(
      [
      actions.source.fetch("/category/nba/").
      actions.source.fetch("/category/boxing/"),
      actions.source.fetch("/category/football/")
      ]
    )
   
  }, []);

  // 2. get data from frontity state
  const dataNba = state.source.get("/category/nba/");
  const dataBoxing = state.source.get("/category/boxing/");
  const dataFootball = state.source.get("/category/football/");

  let categoryNba, categoryBoxing, categoryFootball
  let postsNba, postsBoxing, categoryFootball, postsFootball

  // 3. get entities from frontity state
  if (dataNba.isReady && dataNba.isCategory) {
    const categoryNba = state.source.category[dataNba.id];
    const postsNba = dataNba.items.map(({ type, id }) => state.source[type][id]);
}

  if (dataBoxing.isReady && dataBoxing.isCategory) {
    const categoryBoxing = state.source.category[dataBoxing.id];
    const postsBoxing = dataBoxing.items.map(({ type, id }) => state.source[type][id]);
}

  if (dataFootball.isReady && dataFootball.isCategory) {
    const categoryFootball = state.source.category[dataFootball.id];
    const postsFootball = dataFootball.items.map(({ type, id }) => state.source[type][id]);
}


if (categoryNba && categoryBoxing && categoryFootball)  {
    // 4. only render widgets when all the ingormation is available
    return (
      <>
          <Widget1 category={categoryNba} posts={postsNba} />
          <Widget1 category={categoryBoxing} posts={postsBoxing} />
          <Widget1 category={categoryFootball} posts={postsFootball} />
      </>
    );

}

return null;
};

export default connect(CategoryNature);

this logic will be activated on the Client Side. I you want this info to be available from SSR (you’ll need to move the logic from the useEffect to the beforeSSR action

2 Likes

Hi @juanma. This was a great explanation. And it really works well.

I just wanted to know others that if you fetch these data in beforeSSR then it will increase the initial load time of the page. So the better idea is to use this inside useEffect so that it can fetch APIs once the initial page loading complete.

3 Likes