How to fetch Menu from wordpress?

Hi, I have exposed my site menus and now i want to fetch the menu in my front end.
My menu is exposed at http://localhost:8080/wp-json/wp/v2/menu/primary

1 Like

Hey @aeyzazkhan, welcome to the community :smile:

What plugin are you using to expose the menus?

I am not using any plugin but exposing using custom code in theme functions.php

You can create a custom handler to fetch the content of that endpoint and assign that data to a name to access it later, for example "primaryMenu".

Something like:

libraries.source.handlers.push({
  name: "primaryMenu",
  priority: 10,
  // This pattern is the name you can later use in "actions.source.fetch"
  // to fetch the content or "state.source.get" to get the data.
  pattern: "primaryMenu", 
  // This is the function triggered when you use:
  // actions.source.fetch("primaryMenu");
  func: async ({ state, libraries }) => {
    // Fetch endpoint.
    const response = await libraries.source.api.get({
      endpoint: "menu/primary"
    });

    // Extract data from response object.
    const data = await response.json();

    // Assign data to be consumed later.
    // This is the data returned when you use:
    // state.source.get("primaryMenu");
    Object.assign(state.source.data["primaryMenu"], {
      data,
      isMenu: true,
    });
  }
});

You should add that handler in both beforeSSR and beforeCSR:

const before = ({ libraries }) => {
  libraries.source.handlers.push(menuHandler);
};

export default () => ({
  state: { ... },
  actions: {
    beforeSSR: before,
    beforeCSR: before,
  },
});

Finally, fetch it. If you are going to need it on each route, you can fetch it in the before action:

const before = async ({ libraries, actions }) => {
  libraries.source.handlers.push(menuHandler);
  // This will wait until the primaryMenu data is fetched.
  await actions.source.fetch("primaryMenu");
};

That will ensure that the primaryMenu data is available before the React rendering.

You can consume it like this in your React components:

const Menu = ({ state }) => {
  // Use "state.source.get" to access the data:
  const { data } = state.source.get("primaryMenu");

  return (
    <div>{... use `data` somehow ...}</div>
  );
}

export default connect(Menu);
1 Like

By the way, we will add support for WP menus once this plugin is merged with the core:

1 Like

Hi @luisherranz thanks for your awesome project! I’m just checking this out and playing with the mars and 2019 themes to see if Frontity would fit for my client’s needs. The first thing my client would ask for is if they can change the menu so I’m trying to figure out where to implement this code inside the theme structure to make this possible. I’m using the menu plugin you’ve linked (https://github.com/WP-API/wp-api-menus-widgets-endpoints).

Regards from Tallinn,
Kris

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:

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.