Getting the contents of a specific page?

Hi guys,

this seems like an awfully easy thing to do, yet I seem to have my wires crossed somehow since I have not been able to do this for some days:

All I want to do is get the content of a specific page (that doesn’t match the path).

Say I have a page with the /bio-slug and I want to display it’s content in the <Home /> component. How would I retrieve it?

I have tried to retrieve it utilising state.source.get() in every way, shape or form that seems sensible to me (and then some), but I can’t do it.

Any help much appreciated.

You have several options:

1. Fetch the page if the home has been loaded

This can be a good option if you don’t need server-side rendering for that page. It is also much simpler than creating a new handler (shown later):

const Theme = ({ state, actions }) => {
  // ...
 
  React.useEffect(() => {
    if (state.router.link === "/")
      actions.source.fetch("/bio");
  }, [state.router.link]);

  // ...
};

Then, you can wait until the bio page data is ready to show the items:

const Home = ({ state }) => {
  const data = state.source.get("/");
  const bioData = state.source.get("/bio");
  
  return (
    bioData.isReady &&
      <>
        <Items items={data.items} />
        <BioPage />
      </>
  );
};

Or show them independently:

const Home = ({ state }) => {
  const data = state.source.get("/");
  const bioData = state.source.get("/bio");
  
  return (
    <>
      <Items items={data.items} />
      {bioData.isReady ? <BioPage /> : <Loading />}
    </>
  );
};

2. Fetch the page when the home loads but add server-side rendering

This is similar to the previous one, but with the added benefit of adding server-side rendering.

The Theme and Home components are the same than in the previous one, but you also add a new beforeSSR action:

export default {
  roots: { /* ... */ },
  state: { /* ... */ },
  actions: {
    theme: {
      beforeSSR: async ({ state, actions }) => {
        if (state.router.link === "/") {
          // Stop the server-side rendering (SSR) until this is ready.
          await actions.source.fetch("/bio");
        }
      }
    }
  }
}

This way, if we are doing a server-side rendering of the home page, we make sure the bio page is also in the state.

3. Overwrite the home handler with the new page

This is the most complex one, but it’s another option. The only benefit is that once data.isReady you are sure that the bio page is also ready. You don’t need to check for bioData.isReady in the Home component.

const homeHandler = {
  name: "home",
  priority: 10,
  pattern: "/",
  func: async ({ route, state, libraries }) => {
    const { api } = libraries.source;
    const { page, query } = parse(route);

    // 1. Fetch the specified pages.
    const homePromise = api.get({
      endpoint: "posts",
      params: { search: query.s, page, _embed: true, ...state.source.params }
    });
    const bioPromise = api.get({
      endpoint: "pages",
      params: { slug: "bio", _embed: true }
    });
    const [homeResponse, bioResponse] = await Promise.all([
      homePromise,
      bioPromise
    ]);

    // 2. Populate the state.
    const items = await populate({ response: homeResponse, state });
    await populate({ response: bioResponse });
    if (page > 1 && items.length === 0)
      throw new Error(`post archive doesn't have page ${page}`);

    // 3. get posts and pages count
    const total = getTotal(response);
    const totalPages = getTotalPages(response);

    // 4. Add data to source.
    Object.assign(state.source.data[route], {
      items,
      total,
      totalPages,
      isArchive: true,
      isPostTypeArchive: true,
      isHome: true
    });
  }
};

Then, access the bio data in the <Home> component:

const Home = ({ state }) => {
  const data = state.source.get("/");
  const bioData = state.source.get("/bio");

  // both data and bioData are ready...
};
5 Likes

Hey mate,

thanks for taking the time and writing out this elaborate answer, I really appreciate it other than working very much like intended, it cleared up a misconception I had about how the framework works as well!

Cheers and happy new year!

3 Likes

I’m glad it helped @Konstantin.

Would you mind providing feedback about what changes would you make to the docs, the starter theme or the framework to make this easier to understand? :slight_smile:

Absolutely I can do that, but it might be a little since I’m starting a new job on Monday, am pretty nervous about it and this will eat up a lot of my time, but yeah, I’ll continue to work with Frontity and the docs in my spare time and am glad to provide feedback :slight_smile:

1 Like

No problem. Thanks @Konstantin and good luck with your new job! :tada:

In your 2. solution, do you really need to fetch it in the Theme if you already fetched it in beforeSSR?

In my example I am only fetching the data in the beforeSSR if we are in the home:

if (state.router.link === "/") {
  await actions.source.fetch("/bio");

so the data won’t be there if you start from a different page in the server and then move to the home in the client.


By the way, state.router.link === "/" is not safe as it won’t include requests with queries like "/?query=value" or other pages like "/page/2". We are improving the data object provided by wp-source to make this type of checks easier.

const data = state.source.get(state.router.link);
if (data.path === "/") { // <-- this will be safer
  await actions.source.fetch("/bio");

You will be able to use data.path once this issue is resolved: https://github.com/frontity/frontity/issues/312