Including Components

Up to this point we have all the functionality we need in a single file, but in practice we will of course want to share functionality, similar to how we used components in Ember. We will look at doing this by implementing our standard ARIA-conformant menu.

First create a new directory src/js/components and into that add the following aria-menu.jsx:

import React from "react";

function AriaMenu(props) {
  const keyDown = (ev) => {
    if (ev.keyCode === 39) {
      let nextElement = ev.target.parentElement.nextElementSibling;
      while (nextElement && (nextElement.getAttribute('role') === 'separator' ||
        nextElement.querySelector('*[role="menuitem"]').getAttribute('aria-disabled') === 'true')) {
        nextElement = nextElement.nextElementSibling;
      }
      if (nextElement) {
        nextElement.querySelector('*[role="menuitem"]').focus();
      }
    } else if (ev.keyCode === 37) {
      let previousElement = ev.target.parentElement.previousElementSibling;
      while (previousElement && (previousElement.getAttribute('role') === 'separator' ||
        previousElement.querySelector('*[role="menuitem"]').getAttribute('aria-disabled') === 'true')) {
        previousElement = previousElement.previousElementSibling;
      }
      if (previousElement) {
        previousElement.querySelector('*[role="menuitem"]').focus();
      }
    }
  }

  return (
    <nav>
      <ul className={props.class} role="menu"  onKeyDown={keyDown} aria-label={props.label}>
        {props.children}
      </ul>
    </nav>
  )
}

export default AriaMenu;

The code demonstrates a few features that we will want to discuss. In our previous component we only used useState to store the state to output, but if you look at the code above, you will see that it also uses props. The distinction is that props (properties) are those state values that are set on the component, when the component is called from somewhere else in our application. In the code above, there are two properties props.class to set an optional class name and props.label to set the ARIA label. Additionally there is props.children, which gives access to any nested code, basically doing the same as {{yield}} in an Ember component.

With our ARIA menu implemented, we can now use this in our main application. Update the athena.jsx to use the new component:

import React from 'react';
import ReactDOM from 'react-dom';

import AriaMenu from './components/aria-menu.jsx';
import '../styles/app.scss';

function Athena() {

    ...
  return (
  <div className="container-fluid gx-0">
    <header className="row gx-0">
      <nav className="navbar navbar-dark bg-dark" aria-label="Main">
        <AriaMenu class="navbar-nav horizontal" aria-label="Main">
          <li className="navbar-brand">Athena - Study Portal</li>
          <li className="nav-item">
            <a
              href=""
              role="menuitem"
              tabIndex="0"
              className="nav-link active"
            >
              My Modules
            </a>
          </li>
          <li className="nav-item">
            <a href="" role="menuitem" tabIndex="-1" className="nav-link">
              My Exams
            </a>
          </li>
        </AriaMenu>

        <AriaMenu class="navbar-nav horizontal" aria-label="User">
          <li className="nav-item">
            <a
              href="profile.html"
              className="nav-link"
              role="menuitem"
              tabIndex="0"
            >
              Profile
            </a>
          </li>
          <li className="nav-item">
            <a
              href="login.html"
              className="nav-link"
              role="menuitem"
              tabIndex="-1"
            >
              Logout
            </a>
          </li>
        </AriaMenu>
      </nav>
    </header>
  ...
}

As you can see, to include a nested component, we simply provide the name of the component formatted as an HTML tag. Any attributes we specify on that tag are then available in the component via the props. That is why here we use the class and label properties to pass those values into the component.

Using this inclusion pattern, we can then create arbitrarily large applications. In particular through the use of the routing components that we will use in the next tutorial, we can create different application states.