React Router の Link と Material-UI の ListItem を組み合わせる

Material-UI と React Router を使う SPA で、必ずと言って良いほど書いているので、いい加減メモしておく。

import React from 'react';
import {
  NavLink as RouterLink,
  NavLinkProps as RouterLinkProps,
  useLocation,
} from 'react-router-dom';
import { Omit } from '@material-ui/types';
import {
  ListItem,
  ListItemIcon,
  ListItemText,
} from '@material-ui/core';

export interface ListItemLinkProps {
  icon?: React.ReactElement;
  primary: string;
  to: string;
}

export function ListItemLink(props: ListItemLinkProps) {
  const { icon, primary, to } = props;
  const location = useLocation();
  const selected = location.pathname.startsWith(to);

  const renderLink = React.useMemo(
    () => React.forwardRef<HTMLAnchorElement, Omit<RouterLinkProps, 'innerRef' | 'to'>>(
      (itemProps, ref) => (
        <RouterLink to={to} {...itemProps} innerRef={ref} />
      ),
    ),
    [to],
  );

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