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
- Wrap the
<ListItem/>
in a custom component namedListItemLink
- Add some
propTypes
to theListItemLink
you just defined - Define a
renderLink
component and add that to acomponent
attribute on the<ListItem/>
- 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
- PropTypes
- React Hooks generally
- The
useMemo
hook specifically - Forwarding Refs
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.