Guide to Creating React WordPress menus in a WordPress Theme

Part 3 in a series on Creating a React WordPress theme, In this article we look into Creating a React WordPress menu using the built in WordPress menu functionality.

This post is part of the React WordPress Theme Collection

An Step-by-step guide on creating a React WordPress theme, to list WordPress Rest API posts and pages, display and create comments, integrate Server Side rendering using PHP.

  1. Create a React WordPress theme using babel and webpack
  2. Step-by-step Guide to displaying WordPress Rest API posts in a React WordPress theme
  3. Guide to Creating React WordPress menus in a WordPress Theme

Create and Register a React WordPress Menu

Just like any WordPress theme, in our functions file we will be using register_nav_menus to declare the websites main menu, we will be registering the menu location as main-menu, this will be the main menu reference.

With the WordPress menu registered, go ahead if you havent already and create a new Menu and assign it the location of Main Menu within the WordPress dashboard.

register_nav_menus(array(
    'main-menu' => 'Main Menu'
));

Generate list of WordPress menu items

To display our React WordPress menu we need to retrieve a list of all the menu items for our main-menu, we do this by updating our $config array that is accessible via the React theme, using the yet to be created function jcrt_get_menu.

$config = array(
    //...
    'menu' => jcrt_get_menu('main-menu')
);

The menu structure is generated from the chosen menu location, the location is used to identify the currently active menu, using this menu we fetch all menu items using wp_get_nav_menu_items, which is used to populate the returned menu item data.

/**
 * Get WordPress menu by menu location
 * 
 * @return array
 */
function jcrt_get_menu($location)
{
    $output = [];
    $locations = get_nav_menu_locations();
    $menu = wp_get_nav_menu_object($locations[$location]);
    $menu_items = wp_get_nav_menu_items($menu);

    foreach ($menu_items as $item) {

        $data = [
            'ID' => $item->ID,
            'title' => $item->title,
            'url' => '/' . jcrt_strip_site_url($item->url),
            'parent' => intval($item->menu_item_parent),
        ];
        $output[] = $data;
    }

    return $output;
}

We use the new jcrt_strip_site_url to remove the site prefix from any passed urls, as when we are using react we want links that are relative to the websites root.

/**
 * Remove WordPress site url and trailing slash from url
 *
 * @param $url
 * @return string
 */
function jcrt_strip_site_url($url)
{
    $base_url = site_url();
    $url = trailingslashit($url);
    $url = substr($url, strlen($base_url) + 1);
    if (substr($url, -1) == '/') {
        $url = substr($url, 0, -1);
    }
    return $url;
}

Passing data to React WordPress menus

With our WordPress menu now accessible via our wp_config variable, we need to update index.js and pass the menu data into our App component via React props.

const { menu } = window.wp_config;

ReactDOM.render(
  <BrowserRouter>
    <App menu={menu} />
  </BrowserRouter>,
  document.getElementById('root')
);

With the menu structure inserted into the App component via props, we pass this to a new child component that will render our Menu called NavMenu.

const { menu } = this.props;
<NavMenu items={menu} />

Display React WordPress Menu Items

The new NavMenu component keeps track of the current route via its state, this is used to highlight the current menu item, the component needs access to the React Router we do this by using the withRouter Higher order component.

import { withRouter } from 'react-router-dom';
export default withRouter(NavMenu);

We start by setting the current route when the component mounts

componentDidMount() {
  this.setState({ route: this.props.location.pathname });
}

Then when the the component is updated we check and see if the Websites url has changed, if so we update the active route to match the current url.

componentWillUpdate({ location, history }) {
  if (location.pathname === this.props.location.pathname) {
    return;
  }

  if (history.action === 'PUSH') {
    this.setState({ route: location.pathname });
  }
}

We display the currenty active route by giving the list item an active class when the components route matches that of the displayed menu item, comparing these routes using RegEx making sure the full url is matched.

{items.map((item) => (
  <li
    key={item.ID}
    className={
      'menu__item ' +
      (new RegExp('^' + item.url + '/?$', 'g').exec(this.state.route)
        ? 'menu__item--active'
        : '')
    }
  >
    <Link to={item.url}>{item.title}</Link>
  </li>
))}

src/NavMenu/NavMenu.js

Create the new NavMenu component src/NavMenu/NavMenu.js with the following code.

import React, { Component } from 'react';
import { withRouter, Link } from 'react-router-dom';

class NavMenu extends Component {
  constructor(props) {
    super(props);

    this.state = {
      route: '',
    };
  }

  componentDidMount() {
    this.setState({ route: this.props.location.pathname });
  }

  componentWillUpdate({ location, history }) {
    if (location.pathname === this.props.location.pathname) {
      return;
    }

    if (history.action === 'PUSH') {
      this.setState({ route: location.pathname });
    }
  }

  render() {
    const { items } = this.props;
    return (
      <nav className="header__menu">
        <ul>
          {items.map((item) => (
            <li
              key={item.ID}
              className={
                'menu__item ' +
                (new RegExp('^' + item.url + '/?$', 'g').exec(this.state.route)
                  ? 'menu__item--active'
                  : '')
              }
            >
              <Link to={item.url}>{item.title}</Link>
            </li>
          ))}
        </ul>
      </nav>
    );
  }
}

export default withRouter(NavMenu);

At this point your React WordPress theme should be displaying your WordPress menu, In the next article we will be looking into WordPress permalinks and updating our React Routes to work with Posts, Pages, and custom post types.

Leave a Reply

Fields marked with an * are required to post a comment