Categories


Archives


Recent Posts


Categories


How to Link in React and the Material UI Framework

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

A career in programming involves endless cycles of professional development/retraining. Lately I’ve been working on a hobby project that involves some React. It’s — different. Simple things seem hard and “understanding the magic” requires leaps that aren’t intuitive-to-me. It’s neither traditional web developer or javascript programming while also being both web development and javascript programming.

Also, the learning communities I’ve found so far don’t seem all that interested in what React’s doing to the browser or how React is implemented. A lot of “just do this” cookbook style learning and vague “don’t do this” warnings without a lot of context.

This is less a tutorial and more me writing this all down so I can reference it later. The code samples aren’t fully tested.

The Components

Creating a link in a React application is a two, or perhaps three, step task.

First, you need to create your hyperlink with the <Link/> tag.

Second, you need to create a <Switch/> section in your component tree where the content you’re linking to will go.

Third — both the <Link/> and <Switch/> tags need to be inside a <Router/> tag. The <Switch/> tags will also contains individual <Route/> tags.

The components you’re looking for are in the react-router-dom package.

import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';

You’ll notice the <Router/> tag we mentioned is actually a <BrowserRouter/> tag. There are multiple different router types suitable for different environments. Don’t worry too much about this right now — just know that BrowserRouter is the right (or at least de-facto) choice for a modern, browser based application.

These <Link/> tags will create hyperlinks that “navigate” to different — pages? sections? areas? (take your pick, there doesn’t seem to be consistent terminology) — of your React application. These pages aren’t your traditional web page/full-browser-refresh pages. It’s more of an abstract thing. Clicking the link will update your browser’s URL and history.

By themselves, these links won’t cause anything to change in your application. That’s where the <Switch/> tag comes in. The <Switch/> tag allows you create a section of your application that will render a different React component whenever a user click a <Link/>.

The Code

As previously mentioned, in order to use the <Link/> and <Switch/> tags, you’ll need to wrap a section of your component tree in the <Router/> tag.

import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';

// we're using function based components, but that's not required
function App() {
    return <Router>
        <ul>
            <li><Link to='/some-url'>Section 1</Link></li>
            <li><Link to='/some-url2'>Section 2</Link></li>
        </ul>

        <div>
            <Switch>
                <Route path="/some-url" component={SectionOne}/>
                <Route path="/some-url2" component={SectionTwo}/>
            </Switch>
        </div>
    </Router>
}

When you click on the /some-url link, React will look through the <Route/> tags in your <Switch/> tag until it finds a matching path. Then, it will render the component provided in the component= attribute. React will render this component wherever the <Switch/> tag is located. In order words, click on /some-url, and React will render a tree that looks like this

<ul>
    <li><Link to='/some-url'></Link></li>
    <li><Link to='/some-url2'></Link></li>
</ul>

<div>
    <SectionOne/>
</div>

Material UI

Using a <Router/> can get tricky if you’re using other people’s components. Take Material UI — a (seemingly?) popular third-party-react UI library.

Material UI has their own <Link/> component that won’t work inside a <Router/>

import Link from '@material-ui/core/Link';

They also have patterns/components for building site navigations that don’t use <Link/> tags.

import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';

<ListItem button>
  <ListItemIcon>
    <DashboardIcon />
  </ListItemIcon>
  <ListItemText primary="Dashboard" />
</ListItem>

So how are we supposed to create links here? The Material UI folks have a solution for what they call third-party routing libraries. Both the Material UI <Link/> and <ListItem/> components have a special component property that allows their components to — behave as though they were other components? I’ll admit I don’t fully understand how this works — sometimes you just need to follow the recipe and hope for the best.

There’s one form of this that’s relatively straight forward.

// importing each link with a different name/alias for clarify

import { Link as RouterLink } from 'react-router-dom';
import Link as MaterialLink from '@material-ui/core/Link';

//...
<MaterialLink component={RouterLink} to='/path/to/section'/>

<ListItem button component={RouterLink} to='/path/to/section'>
</ListItem>
//...

Use a MaterialLink, but point to a link component from the react-router-dom to get its behavior. In my testing this works for both @material-ui/core/Link and @material-ui/core/ListItem components.

However, there’s a second form of this — here’s the excerpt from Material UI’s own code samples

import React from 'react';
import PropTypes from 'prop-types';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import { Link as RouterLink } from 'react-router-dom';

function ListItemLink(props) {
  const { icon, primary, to } = props;

  const renderLink = React.useMemo(
    () => React.forwardRef((itemProps, ref) => <RouterLink to={to} ref={ref} {...itemProps} />),
    [to],
  );

  return (
    <li>
      <ListItem button component={renderLink}>
        {icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
        <ListItemText primary={primary} />
      </ListItem>
    </li>
  );
}

ListItemLink.propTypes = {
  icon: PropTypes.element,
  primary: PropTypes.string.isRequired,
  to: PropTypes.string.isRequired,
};

Here Material UI’s recommendation is to

  1. Wrap the <ListItem/> in a custom component named ListItemLink
  2. Add some propTypes to the ListItemLink you just defined
  3. Define a renderLink component and add that to a component attribute on the <ListItem/>
  4. The renderLink component is a — well — a confusing mess to be honest

Stumbling across this as someone new to React, I suddenly need to understand

Or, squint at it for a bit, accept that this somehow magically connects the <ListItem/> with the <RouterLink/>, and paste it in my application hoping I never need to understand this more broadly.

References and Further Reading

Copyright © Alan Storm 1975 – 2021 All Rights Reserved

Originally Posted: 14th March 2021