How to fetch Menu from wordpress?

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

Iā€™m working on the same, and created this workaround (Iā€™m new to React and JS, so feel free to suggest modifications):

const Nav = ({ state, libraries }) => {
  const { items } = state.source.get("menus/2");

  return(
    <NavContainer>
      {items.map(item => {
        const name = item.title.rendered;
        const link = libraries.source.normalize(item.url);
        const isCurrentPage = state.router.link === link;
        const childItems = items.filter( ({parent}) => parent === item.id);

        if(item.parent === 0){ //If it has no parents, it's a top level menu item
          return (
            <NavItem key={item.id} isSelected={isCurrentPage}>
              <Link link={link} aria-current={isCurrentPage ? "page" : undefined}>
                {name}
              </Link>
              { childItems &&  //if it has childrens, put them out now
                childItems.map(childItem => {
                  const childName = childItem.title.rendered;
                  const childLink = libraries.source.normalize(item.url);
                  const isChildCurrentPage = state.router.link === childLink;
                  return(
                    <NavSubItem key={childItem.id} isSelected={isChildCurrentPage}>
                      <Link link={childLink} aria-current={isChildCurrentPage ? "page" : undefined}>
                        {childName}
                      </Link>
                    </NavSubItem>
                  );
                })
              }
            </NavItem>
          );
        }
      })}
  </NavContainer>
  );
};

I also needed to modify the WP-API / menus-endpoints plugin, to remove permissions checks.

Hi,
Do you know what the latest is on this? Iā€™ve only just started using Frontity and React, so would prefer to wait for your implementation if itā€™s coming some time soon.

Hi @stefwilliams

Thanks for your patience!

For the moment, unfortunately, it is still going take a while until there is an official package to manage the menu because it it isnā€™t even in the REST API yet! Iā€™ve seen that they are not planning to include the navigation block in WordPress 5.4 release (https://make.wordpress.org/core/2020/02/07/navigation-block-exclusion-from-wp-5-4/).

The good news is that you have 3 options :slightly_smiling_face::

2 Likes

Hi @SantosGuillamot:
I am trying to create the handler for my WP menus using WP-Rest API. The plugin that you have suggested in your reply is not able to retrieve menus as they have applied permission checks. So I am using another plugin to access the menus and the endpoint is:
My Staging Siteā€™s WP-API menu endpoint

And I followed your guide to create a custom handler like that but I think Itā€™s not fetching menu from the above endpoint, although itā€™s returning the menus.

Custom Handler that I have created:

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: "/wp-api-menus/v2/",

      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

    });

  }

};

My index.js fileā€™s code is:

import Theme from "./components";

import image from "@frontity/html2react/processors/image";

import processors from "./components/styles/processors";

// import { theme } from "@chakra-ui/core";

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: "/wp-api-menus/v2/",

      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 chakraTheme = {

  name: "frontity-chakra-theme",

  roots: {

    // In Frontity, any package can add React components to the site.

    // We use roots for that, scoped to the "theme" namespace.

    theme: Theme

  },

  state: {

    // State is where the packages store their default settings and other

    // relevant state. It is scoped to the "theme" namespace.

    theme: {

      /**

       * The logo can be a text or an image url

       * logo : "Frontity"

       * logo: "https://uploads-ssl.webflow.com/5be00771820599586e6bd032/5be0223588110a6dbcac2d05_image.svg",

       */

      logo: "Frontity",

      showBackgroundPattern: true,

      showSocialLinks: true,

      /**

       * socialLinks: [

            ["pinterest", "https://www.pinterest.com/frontity/"],

            ["facebook", "https://www.instagram.com/frontity/"],

            ["twitter", "https://www.twitter.com/frontity/"]

          ],

       */

      socialLinks: [],

      menu: [],

      featured: {

        showOnArchive: false,

        showOnPost: true

      },

      colors: {

        primary: {

          50: "#e9f5f2",

          100: "#d4dcd9",

          200: "#bbc3be",

          300: "#a1aba5",

          400: "#87938b",

          500: "#6d7972",

          600: "#555f58",

          700: "#323c34",

          800: "#232924",

          900: "#272727"

        },

        accent: {

          50: "#ede4d3",

          100: "#fbe3b2",

          200: "#f6d086",

          300: "#f1be58",

          400: "#eca419",

          500: "#d49212",

          600: "#a5710b",

          700: "#775105",

          800: "#483100",

          900: "#1d0f00"

        }

      },

      isSearchModalOpen: false,

      isMobileMenuOpen: false,

      autoPreFetch: "all"

    }

  },

  // Actions are functions that modify the state or deal with other parts of

  // Frontity like libraries.

  actions: {

    theme: {

      openMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = true;

      },

      closeMobileMenu: ({ state }) => {

        state.theme.isMobileMenuOpen = false;

      },

      openSearchModal: ({ state }) => {

        state.theme.isSearchModalOpen = true;

      },

      closeSearchModal: ({ state }) => {

        state.theme.isSearchModalOpen = false;

      },

      beforeSSR: ({ actions }) => async () => {

        await actions.source.fetch("menus/2");

      }

    }

  },

  libraries: {

    source: {

      handlers: [menuHandler]

    },

    html2react: {

      // Add a processor to html2react so it processes the <img> tags

      // inside the content HTML. You can add your own processors too.

      processors: [image, ...processors]

    }

  }

};

export default chakraTheme;

I resolved the issue as there was issue in using the endpoint and was retrieving via handler name and it was giving error.
Corrected Handler as per @luisherranz guide. Thanks :slight_smile:

const menuHandler = {

  name: "primaryMenu",

  priority: 10,

  pattern: "/wp-api-menus/v2/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: "/wp-api-menus/v2/menus/2",

    });

    // 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

    });

  }

};
2 Likes

Hello everyone,

I am also trying to do the same as everyone in this topic, namely to let the admins manage the menus of the site via WP CMS and then automatically update them on the site.
I have found a plugin already https://wordpress.org/plugins/wp-rest-api-v2-menus/ and I am using the endpoint /menus/v1/menus/<id> to get the main menu.

I followed the guide of creating the custom handler, but I am still a bit confused with the concepts of Frontity so I am not sure exactly what I am doing wrong.

Here is my code from the theme index.js file:

const menuHandler = {
  name: "menus",
  priority: 10,
  pattern: "/menus/v1/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: "/menus/v1/menus/17",
    });

    // 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: items.items, //This is needed because of the structure of the data that my api returns
      isMenu: true
    });
  }
};

const khTheme = {
  name: "@frontity/kh-theme",
  roots: {
    /**
     *  In Frontity, any package can add React components to the site.
     *  We use roots for that, scoped to the `theme` namespace.
     */
    theme: Theme,
  },
  state: {
    /**
     * State is where the packages store their default settings and other
     * relevant state. It is scoped to the `theme` namespace.
     */
    theme: {
      menu: [],
      isMobileMenuOpen: false,
      featured: {
        showOnList: false,
        showOnPost: false,
      },
    },
  },
  /**
   * Actions are functions that modify the state or deal with other parts of
   * Frontity like libraries.
   */
  actions: {
    theme: {
      toggleMobileMenu: ({ state }) => {
        state.theme.isMobileMenuOpen = !state.theme.isMobileMenuOpen;
      },
      closeMobileMenu: ({ state }) => {
        state.theme.isMobileMenuOpen = false;
      },

      beforeSSR: ({ actions }) => async () => {
        await actions.source.fetch("/menus/v1/menus/17");
      }
    },
  },
  libraries: {
    html2react: {
      /**
       * Add a processor to `html2react` so it processes the `<img>` tags
       * inside the content HTML. You can add your own processors too
       */
      processors: [image, iframe],
    },
    source: {
      handlers: [menuHandler]
    }
  },
};

If I log to the console after this call const items = await response.json(); i can see items contains some data for the menu + array of the actual menu items so that seems fine, however it doesnt get rendered on the front end. When i check frontity.state.theme.menu it is empty. I am just a bit confused do I need to set the state or this is taken care of.

Any help will be apprciate.

1 Like

@sarang can you please help here?

Thanks

It seems you set the data from a response to a specific route, so the data should not be inside frontity.state.theme.menu, instead it should be available in frontity.state.source.data[YOUR_ROUTE]

hey @dev.fedorenko. Thanks a lot for the help. To be honest, i should have read a bit better how wp-source works. I am starting to get it now and I think the concepts of Frontity are amazing and I am quite excited to work more with it.

I do have another question, related to how Frontity is meant to be used. In my case, I can confirm that what you said is working. I can access my menu items via frontity.state.source.data[YOUR_ROUTE], however I am not sure what would be the best way to manage the menu now. Should I populate frontity.state.theme.menu with my menu items or should i directly use them in my component from frontity.state.source.data[YOUR_ROUTE]. I just really want to use Frontity in the best way possible.

Thanks in advance.

So the earlier posts here do not quite work and only @ni.bonevā€™s response seems to do the trick. However, Iā€™m after something that can be truly modular, not just getting one set-in-stone menu but something truly customizable on the back end and re-usable from project to project.

My goal is to create a theme that has both ā€œNavigation Bar Menuā€ (with slug "primary-menu") and a ā€œFooter Site Mapā€ (with slug "footer-menu") menu locations. These locations are configured in the back end so that the user can use the menu capabilities of WordPress to their full extent and swap menus in and out of these locations, which are then included in the beforeSSR actions so they are available on each page. Hereā€™s how I did it:

  1. Install the WP-REST-API V2 Menus plugin (the other plugin mentioned in the thread requires authentication to grab the menu items so no longer works).

  2. Add this menu handler:

    const menuHandler = {
      name: 'menus',
      priority: 10,
      pattern: '/menus/:slug', // You can use something shorter here, you don't need to use the endpoint
      func: async ({ route, params, state, libraries }) => {
        const { api } = libraries.source
        const { slug } = params
    
        // 1. fetch the data you want from the endpoint page
        const response = await api.get({
          endpoint: endpoint: `/menus/v1/locations/${slug}` // Instead of using params, this plugin requires a slug be part of the route. We are also using "locations" to get menu locations instead of a specific menu
        })
    
        // 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, {
          slug,
          items: items.items, // @ni.bonev figured this one out because the "items" property contains "items" that are the menu items we are after
          isMenu: true
        })
      }
    }
    
  3. Add it to your themeā€™s beforeSSR and source properties:

    export default {
      name: 'my-fantastic-theme',
      roots: {
        ...
      },
      state: {
        theme: {...}
      },
      actions: {
        theme: {
          beforeSSR: async ({ actions }) => {
            // Adding both menu location menus to beforeSSR
            await actions.source.fetch('/menus/primary-menu')
            await actions.source.fetch('/menus/footer-menu')
        }
      },
      libraries: {
        source: {
          handlers: [menuHandler] // Adding the handler here
        }
      }
    }
    
  4. Then you can do awesome stuff like create a <Header> component that just uses const {items} = state.source.get('/menus/primary-menu') for the navigation bar header and a <Footer> component that uses const {items} = state.source.get('/menus/footer-menu'). It even effortlessly grabs child items (much easier than I used to do it in PHP WordPress themes).

Itā€™s not perfect but for the time-being, this might help others out there who also want to use the menu editing built into WordPress.

6 Likes

And did you created a two-level navigation menu? Can you share your solution for that with us?

Hey David,

thanks a lot on improving on what we had already figured out. I will definitely implement it on my side as I really like using the menu locations.

I have made lots of improvement to my menu including child items and some extra acf fields. It looks a bit like a mega menu with dropdowns and so on. Once it is finished I can extract it in a package and share it, but for the most part, it is nothing unique/special. Just implementing the dropdown menus in a usual way.
Getting the sub-menu items is super easy as each menu item object in the API response, includes child_items.

Wish you good luck.

1 Like

I will share my solution but as @ni.bonev states, itā€™s really quite simple with the use of child_items. So here is my <Header> component:

import React from 'react'
import {connect} from 'frontity'

import Link from '../partials/link'
import NavLink from '../partials/nav-link'
import DropdownNavLink from '../partials/dropdown-nav-link'

const Header = ({state}) => {
  const {items} = state.source.get('/menus/primary-menu')
  const areThereLinks = items != null && items.length > 0

  return (
    <header>
      <Link href="/">My Fabulous Website</Link>

      <nav>
        {areThereLinks &&
          items.map((item, index) => {
            const {child_items} = item

            if(typeof child_items !== 'undefined' && child_items.length > 0) {
              return <DropdownNavLink navLink={item} key={index} />
            }

            return <NavLink navLink={item} key={index} />
          })}
      </nav>
    </header>
  )
}

export default connect(Header)

My <NavLink> component is actually used for styling navigation bar links separately from regular links and adding a closeMobileMenu click handler but I removed those styles for this code sample for simplicityā€™s sake:

import React from 'react'
import {connect} from 'frontity'

import Link from './link'

const NavLink = ({navLink, state, libraries}) => {
  const {url, title} = navLink
  const link = libraries.source.normalize(url)

  return (
    <Link
      href={link}
      active={state.router.link === link}
      aria-current={state.router.link === link ? 'page' : undefined}
    >
      {title}
    </Link>
  )
}

export default connect(NavLink)

And then a dropdown menu item uses <DropdownNavLink> elements (again with styles removed for simplicity):

import React, {useState} from 'react'
import {connect, styled} from 'frontity'
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import {faAngleDown} from '@fortawesome/free-solid-svg-icons'

import NavLink from './nav-link'

const DropdownNavLink = ({navLink, closeMobileMenu}) => {
  const {title, child_items} = navLink
  const [dropdownShown, toggleDropdown] = useState(false)

  return (
    <div
      onClick={() => toggleDropdown(!dropdownShown)}
      onMouseEnter={() => toggleDropdown(true)}
      onMouseLeave={() => toggleDropdown(false)}
      showDropdown={dropdownShown}
    >
      <button onClick={() => toggleDropdown(!dropdownShown)}>{title} <FontAwesomeIcon icon={faAngleDown} /></button>

      <Dropdown show={dropdownShown}>{child_items.map((navLink, index) => (
        <NavLink key={index} navLink={navLink} closeMobileMenu={closeMobileMenu}>{title}</NavLink>))}
      </Dropdown>
    </div>
  )
}

export default connect(DropdownNavLink)

const Dropdown = styled.div`
  position: absolute;
  display: ${props => props.show ? 'flex' : 'none'};
  flex-direction: column;
`

But itā€™s all quite simple to compose with components and I could probably write it better the second time.

2 Likes

Hello Luis,

I tried the code, but the following part generates an error.

Object.assign(state.source.data["primaryMenu"], {
      data,
      isMenu: true,
    });

It is because there is no property primaryMenu on state.source.data. Am I missing something? When I first assign a empty object (state.source.data["primaryMenu"] = {}) the error is resolved.
But when I want to access the data (const { data } = state.source.get('primaryMenu');) in my component I always get undefined.