How to fetch Menu from wordpress?

These are our plans for wp-source:

  • Phase 0 (finished): custom handlers support
  • Phase 1 (finished): default handlers for common WP urls (home, categories, tags, posts, pages)
  • Phase 2 (EDIT: finished now!): handlers for other URLs (custom post types, custom taxonomies) and settings for common WP configurations (subdirectory, page as homepage…). These things need to be configured in the frontity.settings.js file.
  • Phase 3 (EDIT: started design phase): handlers for non-URL data, like menus, comments, attachments, widgets…

Our idea for the design of phase 3 is that non-URL data work with the same APIs than the URL data. It just doesn’t start with “/”.

For example, comments will be something like:

// Fetch comments for post 123:
actions.source.fetch("comments/123")
// Get data for those comments
const data = state.source.get("comments/123")
// Iterate over the comments
data.items.map(item => {
  const comment = state.source.comments[item.id]
  // Do stuff
})

Similar for menus, with something like:

// Fetch all menus
actions.source.fetch("menus")
// Fetch specific menu
actions.source.fetch("menus/my-top-menu")

We will open a RFC (request for comments) here in the forum to gather feedback before the final implementation.

In the meantime, you can create your own custom handler to get your menus using any name you want linked to an API endpoint as explained here in this topic. And if you have any question let us know and we’ll help you out :slight_smile:

3 Likes

Thanks for the in-depth explanation! :ok_hand:

1 Like

@luisherranz, I am very interested in this method and tried myself to implement menu on mars theme. But it’s not working. When fetching, it returns null object. I think it is due to the api endpoint.

Can you please let me know how to set endpoint to get menu? For instance, I am going to get all menus and REST API entrypoint is: http://localhost/wp-json/wp/v2/menus/. Then how should I set the endpoint.

I hope to get response from you asap.

Regards,

@luisherranz

My case is that I have bilingual site (czech, english). Default language is czech I have a frontpage handler ( “/” ) and other handlers ("/:slug"). These pages read from state.lang = “cs” and fetch everything according to this setting.

I bumped into a trouble when user goes for routes “/en/” or “/en/:slug”. Where this time handlers change state.lang property to “en”. But my navigation (menu) stays prefetched in czech language.

How can I fetch the menu from WITHIN a component (navigation.js), which is loaded from index.js > header.js ?

This is what I have now: https://ibb.co/cLv37hZ
This is what I would like to have, but it does not work that way:
https://ibb.co/tPPFGwz

@jin.yin1229 What PHP plugin are you using?

@aeyzazkhan, @kris, @afatoga, @jin.yin1229 we have started the design of handlers for non-URL and one of the things we want to add is support for a REST API menu plugin:

Could you please take a look and share with us your impressions?

1 Like

could someone tell me how to implement this?

It really depends on the plugin you use.

I’ll make an example for the one that is supposed to be the one WP will use, but it’d be similar for others.

Btw, we are going to work in a Frontity package for this but it doesn’t mean it’ll be like this example, it is an implementation you could try meanwhile.

  1. First you have to install this plugin (or the one you want) to show the menus on the REST API
    https://github.com/WP-API/menus-endpoints
  2. You have to create a handler to fetch this data pointing to the new endpoints.

For doing so you have to create it at the index.js file of your theme, or create a new package exclusively for this. At this file you should fetch the data and populate the state as you want, something similar to this:

const menuHandler = {
  name: "menus",
  priority: 10,
  pattern: "menus/:id",
  func: async ({ route, params, state, libraries }) => {
    const { api } = libraries.source;
    const { id } = params;

    // 1. fetch the data you want from the endpoint page
    const response = await api.get({
      endpoint: "menu-items",
      params: {
        menus: id,
        per_page: 100 // To make sure we get all elements
      }
    });

    // 2. get an array with each item in json format
    const items = await response.json();

    // 3. add data to source
    const currentPageData = state.source.data[route];

    Object.assign(currentPageData, {
      id,
      items,
      isMenu: true
    });
  }
};

const marsTheme = {
  name: "@frontity/mars-theme",
  roots: { ... },
  state: { ... },
  actions: { ... },
  libraries: {
    ...
    source: {
      handlers: [menuHandler]
    }
  }
};

export default marsTheme;

This way, we have already created our handler. In order to access the data we first have to fetch it using actions.source.fetch("menus/:id"), the state would be populated and we can access it with state.source.get("menus/:id").

This implementation allows you to select by id the menu you want to fetch. For example, if you want to get the data from menu 54, you have to use actions.source.fetch("menus/54") and state.source.get("menus/54").

  1. If you want to fetch a menu for all pages, you can fetch it from your theme too, adding it to actions. This way, the data will be accessible from the beginning. If you want to do so, you have to include it also at index.js file. It should be something like this:
const menuHandler = {
  name: "menus",
  priority: 10,
  pattern: "menus/:id",
  func: async ({ route, params, state, libraries }) => {
    const { api } = libraries.source;
    const { id } = params;

    // 1. fetch the data you want from the endpoint page
    const response = await api.get({
      endpoint: "menu-items",
      params: {
        menus: id,
        per_page: 100 // To make sure you fetch all the elements
      }
    });

    // 2. get an array with each item in json format
    const items = await response.json();

    // 3. add data to source
    const currentPageData = state.source.data[route];

    Object.assign(currentPageData, {
      id,
      items,
      isMenu: true
    });
  }
};

const marsTheme = {
  name: "@frontity/mars-theme",
  roots: { ... },
  state: { ... },
  actions: { 
    ...
    theme: {
      beforeSSR: ({ actions }) => async () => {
        await actions.source.fetch("menus/54");
      }
    }
  },
  libraries: {
    ...
    source: {
      handlers: [menuHandler]
    }
  }
};

export default marsTheme;
  1. Once you have fetched your menu and it’s available in the state, you just have to consume its data as you want. For example, you could adapt the <Nav /> component of mars-theme:
const Nav = ({ state, libraries }) => {
  const { items } = state.source.get("menus/54");

  return (
    <Container>
      {items.map(item => {
        const name = item.title.rendered;
        const link = libraries.source.normalize(item.url);
        return (
          <Item key={name} isSelected={state.router.link === link}>
            <Link link={link}>{name}</Link>
          </Item>
        );
      })}
    </Container>
  );
};

export default connect(Nav);

With all of these, you should have your menu working fetching the data from WordPress. I hope you find this useful and please, let us know if you have any questions :slightly_smiling_face:

5 Likes

The new Nav block will include support to import existing menus. That means the Gutenberg team needs to add the /menus endpoint to the core, so things started to get in motion.

Jonny Harris (spacedmonkey) has taken over the task and he is actively working on the plugin to make it ready. As soon as the final API is frozen and ready to be merged, we will add support for it.

Figured I’d post here instead of creating another thread. I’m using this plugin to expose my menu endpoints and it’s working well but I’ve run into bit of a performance snag while trying to load multiple menus.

On every page of this site we need to retrieve 4 seperate menus (primary/top nav and 3 footer menus), our menu structure looks like this:

{
    "primary": [{
        "name": "Company",
        "items": [{...}]
    },{
        "name": "Contact"
    }],
    "footer_left": [{
        "name": "About"
    },{
        "name": "Contact"
    }],
    "footer_middle": [{
        "name": "Lorem ipsum"
    },{
        "name": "Blah"
    }],
    "footer_right": [{
        "name": "FAQ"
    },{
        "name": "Blah"
    }]
}

I noticed the plugin we’re using can retrieve all menu item’s irrespective of a menu which is fine, but the issue with this approach is; it doesn’t return the menu it actually belongs to so I can no longer determine what menu item’s should appear where.

Currently we’re performing 4 seperate requests to the WP API in order to populate Frontity and render menus, which for each request will obviously add a delay to page load.

Doing 3 seperate requests doesn’t seem like the correct way to do this so I had 2 questions:

  • Is a menu even the right approach for this?
  • Can I retrieve all the menu items (and their children) with a single request with knowledge of what menu they belong to?

Even running both Frontity and WP locally on the same machine, the additional requests add a noticeable load time to the page (although probably more related to my machine).

This will probably be better once some form of caching is in place and the site is live but I wanted to make sure I’m not doing something stupid and putting a bandaid over it.

Hi @chris!

I don’t know if having three menus for this is the best option but I guess it’s a good approach. As far as I know, if you are using the plugin you mentioned, you could add the query _embed=true in your menu endpoint and it will add the menu each item corresponds to, so you can get all the items in just one fetch.

The fetch should be something like https://mysite.com/wp-json/wp/v2/menu-items?_embed=true and you can find the id, name, slug of the menu it belongs to at the _embedded field.

I think that with this you can just make one fetch instead of four and your performance will improve. Note that the API is restricted to 100 items, so if you have more menu-items you may need to make more than 1 fetch.

Please, let us know if this helps! We’d love to know more about what you are building with Frontity :relaxed:

@SantosGuillamot is there a way to get the three menus and its items without fetching all the items?

Maybe something like https://mysite.com/wp-json/wp/v2/menu-items?_embed=true&include=1,5,12 to retrieve menus with IDs 1, 5 and 12.

If that’s not possible maybe we can try to add it before it gets merged with the core.

Apart from that, @chris, make sure you’re doing your fetching in parallel with something like Promise.all.

Thanks @SantosGuillamot I didn’t know about the _embed flag. It looks like that’ll may solve my issue, I’ll have a play with it.

Interested to hear what you think these should be if not menus.

@luisherranz Thanks for the reply. I agree it would be ideal if I could just get a subset of the menu-items like that.

I can’t reveal too much about the project until it’s released, but with the amount of traffic expected to flow through the site we’re trying to optimise things like this as much as possible.

Edit:
Looks like you can retrieve specific menus using:
http://example.com/wp-json/wp/v2/menu-items?menus=1,2,3,4

Which will actually solve my issues, I don’t know how I didn’t find this earlier…

Edit #2:
Spoke too soon :slightly_frowning_face: Looks like there is an issue doing it this way. If the same menu item belongs to more than 1 menu it won’t pull through both menu-items (i.e. About page belongs in the header and footer menu). In addition, appending _embed=true to the request only shows the 1 menu as being associated with the menu-item.

Perhaps this isn’t meant to work this way. I’ll need to look into this a bit more.

Great! Just let us know once it’s finished :relaxed:.

Nice! It seems to be the correct way to do it then

I’ve done a quick test and in my case the response shows both menu-items, with different ids, belonging to two different menus. And each _embedded field shows a different menu associated.

Maybe it is not showing because if you don’t use the query per_page it only shows 10 items. You could try to use per_page=100 to make sure you get all the items you want.

A quick summary of the discussion as some important aspects are kind of lost:

  • You can fetch different menus in the same call selecting the ones you want with the query ?menus=1,2,3,4 and it will bring all the items of that menus.
  • I would recommend to include also the query ?per_page=100 to make sure you get all the items. Note that if you have more than 100 items you’ll need to make another fetch because WordPress REST API is limited to that. You’d need to include page=2.

Hope you find this useful @chris :relaxed: , please let us know if you find more cases not covered here.

Hi, thanks for your explanation,

I have followed all the steps and now I can see my WordPress menu in my frontity app :slight_smile:

I only have one question, how can I have multiple levels in the menu?

For example, in my WordPress I have configured a menu option with several children options, but in frontity all the options appear in the same level.

Do you have any suggestions to help me with this?

Thanks.

1 Like

Thanks @SantosGuillamot setting per_page did the trick!

I’m yet to read the WP API documentation in detail, I had no idea about _embed or the fact there was limit in the number of items it would return by default. Is there some sort of cheat sheet with all these parameters that I can refer to?

@chris

I know It is very late for this answer but almost any REST API should have documentation in it which can be viewed by entering the mail link of the API.

This will return a JSON with all the endpoints, the parameters for each one and a a brief description for every thing.

For example when you visit the wordpress documentation site REST link as following you will see this

OR simply go to the documentation site to read the same thing but I love the first way because I can search easily for any thing.

Hope this helps :slight_smile:

1 Like

Thanks for this small tutorial!
I tried to implement it, but the REST api says: Sorry, you cannot view these menu items, unless you have access to permission edit them.
What do I missed?
p.s.: I’m totally new to Frontity, this is the first thing, I try to change in the Mars-theme

1 Like

Unfortunately, they have decided to make the menu endpoint private: https://github.com/WP-API/menus-endpoints/pull/40