How to fetch Menu from wordpress?

Hey @david1,

Appreciate the thorough response! I see - I was confused in looking at the docs and didnā€™t read closely enough that beforeSSR is just a function triggered at a certain time by Frontity that you can tap into. I read the examples as method signatures, my fault. Again greatly appreciate the help!

1 Like

Hi @mburridge,
Thank you for the quick feedback. I was actually watching a video with you in it last night where you were delving into what Frontity objects you can inspect in the console, very helpful! Thank you!

2 Likes

Hi Michael @mburridge,
Ok more questions :grinning:
Also please let me know if I should be creating new posts for these or if there is a specific protocol. My next questions are related to my WP data source. Iā€™m using a locally run version of WordPress. Here is the code from my frontity.settings.js

{
ā€œnameā€: ā€œ@frontity/wp-sourceā€,
ā€œstateā€: {
ā€œsourceā€: {
ā€œurlā€: ā€œhttps://ripple-web.lndo.site/ā€,
}
}
},

With this setup I get an error on the server console like this:

`FetchError: request to https://ripple-web.lndo.site/wp-json/wp/v2/posts?_embed=true&slug=footer-about-us failed, reason: unable to verify the first certificate`

I can use a locally run version of the site with a non https url:

`http://ripple-web.lndo.site/`

When I do this my frontity site loads the header and footer but in the middle of the page I get the ā€™ Oops! Something went wrong message. The error in the browser console is:

GET http://ripple-web.lndo.site/wp-json/wp/v2/posts?_embed=true&page=1 401 (Unauthorized)

Obviously these are 2 separate issues but either way Iā€™m not able to connect to my data source. Any help or direction greatly appreciated! Thanks!

Hi @sattley

Iā€™m not an expert on web security, but I found this answer about adding the root certificate to your application.

This may guide you in the right direction, or alternatively turn off https for that site on your local host. As itā€™s just local on your dev machine you donā€™t necessarily need https/ssl unless youā€™re specifically developing something that depends on it.

Hi @mburridge,
Thanks for the quick response. I fear Iā€™m getting pretty despondent with development so far, seem to be so many errors, though I feel like the majority of it is because its WordPress :slight_smile:

Iā€™d love any help, so here is where Iā€™m at currently. Iā€™m trying to output some menu content like other people in this thread.

  • Iā€™ve set my state.source.url to my locally running wordpress site which uses http. So Iā€™m no longer getting that error about the certificate.

  • In the beforeSSR action I have this code:

    beforeSSR: async ({actions}) => {
    await actions.source.fetch('http://ripple-web.lndo.site/wp-json/menus/v1/menus/footer-about-us') // this invokes our footerMenuHandler
  }
  • The resource at this address:
    ā€¦/wp-json/menus/v1/menus/footer-about-us returns the correct JSON I need.

  • I get this error:

ServerError: post type from endpoints ā€œposts,pages,mediaā€ with slug ā€œfooter-about-usā€ not found
at Object.eval (webpack-internal:///./node_modules/@frontity/wp-source/src/libraries/handlers/postType.ts:37:21)
at runMicrotasks ()
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at eval (webpack-internal:///./node_modules/@frontity/wp-source/src/actions.ts:24:1) {
status: 404,
statusText: ā€˜post type from endpoints ā€œposts,pages,mediaā€ with slug ā€œfooter-about-usā€ not foundā€™
}

Iā€™d love any help on this, thanks!

Hi @sattley

Enter the URL http://ripple-web.lndo.site/wp-json/menus/v1/menus/footer-about-us into a browser address bar. Do you get anything? i.e. do you know thatā€™s the correct endpoint?

Hi @mburridge,
Yep, itā€™s the correct endpoint - it returns me the JSON I need for my menu. Here is a partial screen grab of whatā€™s returned in the browser. I cropped it since Iā€™m guessing itā€™s enough to show itā€™s working.

Hi @sattley

Could you provide a link to a repo please. How have you implemented your handler? Funnily enough the next episode of Frontity Talks is going to be about this very topic, i.e. fetching menus from WP. It should be available at the end of next week.

Hi @mburridge,
Apologies for not getting back sooner. I was determined to get this working and I finally got everything did! I couldnā€™t share a repo because itā€™s a private company project. Tbh me figuring it out was more a result of just diving into the code more to understand what was really happening.

A few things did help me though, Iā€™ve listed them below in case others read this:

I used this plugin to expose the menus:


This plugin exposes the following routes and itā€™s important to be mindful of these paths.
  • /menus/v1/menus list of every registered menu.
  • /menus/v1/menus/<slug> data for a specific menu.
  • /menus/v1/locations list of every registered menu location in your theme.
  • /menus/v1/locations/<slug> data for a specific menu location.

Handler Code
In my handler when I was fetching the data from the source:

const response = await libraries.source.api.get({ 
  endpoint: `/menus/v1/menus/${slug}`
});

I initially had the endpoint starting without a /. I later read on another post the following:

If the "endpoint" value doesnā€™t start with "/", it will add "/wp/v2" first. So when my endpoint should have been:

https://ripple-web.lndo.site/wp-json/menus/v1/menus/footer-about-us

it was actually:

https://ripple-web.lndo.site/wp-json/wp/v2/menus/v1/menus/footer-about-us

Which was incorrect.

Handler Code and beforeSSR action

Lastly I think I had the path / route in my actions.source.fetch call incorrect in the theme actions. Here is my handler code and my beforeSSR call in my marsTheme config that works.

> actions: {
>     theme: {
>       // special Frontity action fired to prepare the state for the React render made in the server
>       beforeSSR: async ({actions}) => {
>         await actions.source.fetch('menus/footer-menu') // /menus/v1/menus/<slug> data for a specific menu location.
>       },
>       toggleMobileMenu: ({ state }) => {
>         state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;
>       },
>       closeMobileMenu: ({ state }) => {
>         state.theme.isMobileMenuOpen = false;
>       },
>     },
>   },

// handler

export const footerMenuHandler = {
  name: "footerMenu",
  priority: 10,
  pattern: "menus/:slug",
  func: async ({ route, params, state, libraries, force }) => {

    console.log(params.slug);
    console.log("Route: " + route);

    const { slug } = params;
    console.log("Slug: " + slug);
    // 1. fetch the data you want from the endpoint page
    const response = await libraries.source.api.get({ 
      endpoint: `/menus/v1/menus/${slug}`
    });

    // this is where we get the actual data
    // 2. get our menu object in json format
    const menu = await response.json();

    console.log(menu);
    // this is a reference to the state object for this link currently, get this and it wil be the target object which
    // we assign our data from items to in object.assign
    // 3. add data to source
    const currentPageData = state.source.data[route];

    Object.assign(currentPageData, {
      slug,
      items: menu.items, // @ni.bonev figured this one out because the "items" property contains an array of items
      isMenu: true
    });

  }
};

I believe the pattern in the handler and the route in my actions.source.fetch need to coordinate or match if you will. The menu Iā€™m fetching is a menu created in WordPress however I had to register it in php with the register_nav_menu WordPress function in order to register it with the slug I wanted, in this case footer-menu

php code to register the menu with the slug I wanted. Though I donā€™t think I had to do this . . .

//add footer menu
function register_footer_menu() {
  register_nav_menu('footer-menu',__( 'Footer Menu'));
}
add_action( 'init', 'register_footer_menu');

Anyway long and short of it is that now itā€™s working like a charm and Iā€™ve built my sites footer :slight_smile: Thanks again!

2 Likes

Awesome work @sattley. The solution youā€™ve come up with is very similar to the one weā€™ll be presenting in the next episode of Frontity Talks.

Thanks for sharing your solution in the community forum. Others will benefit, Iā€™m sure.

2 Likes

@mburridge Looking forward to the video!! Definitely could not have gotten my code working without the countless other comments on this thread :facepunch: :smiley: I also very much appreciate your help and the rest of the Dev Rel team!

2 Likes

Hi @sattley

:film_strip: The video is now up: https://www.youtube.com/watch?v=BMJn0RZ2I9s

1 Like

@mburridge maybe it makes sense to create an elaborated answer in this thread pointing to the resources youā€™ve created today for the menus and mark it as the solution for this thread?

This way people accessing the thread will have an easy way to find your answer :slight_smile:

3 Likes

Sure thing @Pablo. :+1:

As well as the video tutorial in the Frontity Talks series explaining the process to follow to get the menu from WordPress and use it in your Frontity project, thereā€™s also this accompanying code demo.

The code demo uses mars-theme which is largely unaltered apart from the additional code needed to fetch and use the menus.

The additional files are the handlers folder and the file menu-handler.js which is contained within it.

This is the handler code:

const menuHandler = {
  name: "menus",
  priority: 10,
  pattern: "/menu/:slug",
  func: async ({ link, params, state, libraries }) => {
    console.log("PARAMS:", params);
    const { slug } = params;

    // Fetch the menu data from the endpoint
    const response = await libraries.source.api.get({
      endpoint: `/menus/v1/menus/${slug}`,
    });

    // Parse the JSON to get the object
    const menuData = await response.json();

    // Add the menu items to source.data
    const menu = state.source.data[link];
    console.log(link);
    Object.assign(menu, {
      items: menuData.items,
      isMenu: true,
    });
  },
};

export default menuHandler;

This is the only new file in the theme. The other changes are in existing files.

In index.js the handler is imported (line 5) and added to the libraries.source.handlers array (line 60). Also a beforeSSR action is added *(lines 45 - 47).

Import of handler and added to array:

// ...
import menuHandler from "./components/handlers/menu-handler";

// ...

libraries: {
  // ...
  source: {
    handlers: [menuHandler],
  },
},

This is the beforeSSR action method:

actions: {
  theme: {
    // ...
    beforeSSR: async ({ state, actions }) => {
      await actions.source.fetch(`/menu/${state.theme.menuUrl}/`);
    }
  },
},

The menu items are then rendered in nav.js where some styling can also be applied. How you implement and style this part is up to you and the requirements of your project.

4 Likes

Hello together,

I tried to follow this example step by step. The video is really good and the Github as well. I think I have ruled out any typos etc.

Unfortunately I get in the console

errorStatusText: ā€œpost type from endpoints ā€œposts,pages,mediaā€ with slug ā€œmain-menuā€ not foundā€

Am I doing something wrong? Has anyone else a similar issue?

Best Regards
Michael

Hey Michael,

based on what I see I am assuming that the request is being handled by the default handlers of frontity.
You have to make sure you create the menu handler, so when you call api.get on the menus URL, frontity knows which url to request.

If there is no handler, frontity by default is going to attempt to fetch from the 3 default post type endpoints: posts, pages, media.

Here is how my menu handler looks like:

const menuHandler = {
  name: "menus",
  priority: 10,
  pattern: "/menu/:slug",
  func: async ({ link, params, state, libraries }) => {
    const { slug } = params;

    // Fetch the menu data from the endpoint
    const response = await libraries.source.api.get({
      endpoint: `/menus/v1/menus/${slug}`,
    });

    // Parse the JSON to get the object
    const menuData = await response.json();

    // Add menu items to state.source.menu
    Object.assign(state.source.menu, {
      [slug]: menuData.items,
    });

    // Add the metadata for menus
    const menu = state.source.data[link];

    Object.assign(menu, {
      isMenu: true,
    });
  },
};

export default menuHandler;

Depending on how you are storing your menu items, the last part could be a bit different.

Hope that helps. Good luck.

1 Like

Hello,

Thank you for your input.

I found a solution by simply doing an extra axios request within the nav.js.

Not the most elegant but it did work for me without digging deeper into the handler.

That works as well. Just 1 thing I would recommend, dont use axios.
Frontity already 2 ways that it can help you avoid this. Both are safe on the server and one of them is made specifically to work for frontity.

  1. Fetch your data: @frontity/wp-source - API Reference
  2. Populate the state: @frontity/wp-source - API Reference

This will keep your site lighter and faster. Alternatively you can use the WHATWG API implementation of fetch that is included in frontity package: frontity - API Reference