# Building External and Internal Next.js Routing Link Components For Material UI

[Material-UI](https://material-ui.com/) is an excellent UI library of React Components following the [Material Design spec](https://material.io/design/). Even if you don't adhere to the Material Design spec, Material-UI is super customizable for your specific needs. 

A few of the great features of Material-UI include:
- The ability for Buttons, etc to easily switch between using html anchor elements and html buttons for better semantic markup without having separate components. See `component` in [Button API](https://material-ui.com/api/button/)
- The ability for Buttons, etc to use custom components to enable 3rd party routing, etc. See [Composition with 3rd Party Library](https://material-ui.com/guides/composition/#button)  

The combination of the above features gets a bit murky when you are trying to leverage TypeScript. Since it took me a few hours and I couldn't find any definitive examples easily, I thought I'd share my approach. 

## Goals
 - Create an ExternalLink and InternalLink component that can be used with various Material-UI components such as Button.
 - The InternalLink component should wrap the [Next.js Link](https://nextjs.org/docs/api-reference/next/link) component enabling routing.
 - Support all the normal HTML attributes for anchors including `target`, `rel`, `title`, etc as well as data attributes.
 - Implement `onClick` events for both components to allow for analytic event tracking, closing menus, etc.
 - Allow children of either component to be strings or other components
 - No TypeScript compiler errors or warnings in strict mode.

## ExternalLink
I started here thinking it would be "easy" to wrap a simple HTML anchor tag. However, it was a bit of a challenge to figure out the types. Here is the component I settled on.

```
import React from 'react';

function clickHandler(e: React.MouseEvent): void {
  // Add your custom event handler code here - record analytics, etc
  console.log('ExternalLink.clickHandler:', e);
}

interface ExternalLinkProps extends React.HTMLProps<HTMLAnchorElement> {
  href: string; // Forces href to be required - see "Gotchas"
  children: React.ReactNode; // Forces children to be required
}

// eslint-disable-next-line react/display-name
const ExternalLink = React.forwardRef<HTMLAnchorElement, ExternalLinkProps>((props, ref) => {
  // Peel off the onClick handler if given
  const { onClick, ...rest } = props;

  const wrappedOnClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void => {
    if (onClick) {
      // If the consumer passed in onClick, call it
      onClick(e);
    }

    // Finally, call our External Handler
    clickHandler(e);
  };

  return <a ref={ref} {...rest} onClick={wrappedOnClick} />;
});

export default ExternalLink;
```
The key points here:
 - Material-UI requires a component that can accept a `ref`. As such, we have to wrap our component in a `React.forwardRef` and pass it to the anchor.
 - Our type definition `ExternalLinkProps` extends `React.HTMLProps<HTMLAnchorElement>` which allows for all the HTML anchor attributes. However, I am enforcing the `href` and `children` to be required. See Gotchas below for more details.
 - We intercept the `onClick` so we can wrap it if given so we can extend click handler functionality. 
 - All other props are forwarded down to the HTML anchor

## InternalLink
The key difference with this component is that I needed to support Next routing. The Next `Link` component takes in a series of props and binds to its children to enable routing. As such, it has to wrap something. 

```
import React, { ReactNode, useContext } from 'react';
import NextLink from 'next/link';

interface InternalLinkProps extends React.HTMLProps<HTMLAnchorElement> {
  href: string; // Forces href to be required - see "Gotchas"
  as?: string; // Optional Next.js property to support dynamic routing
  children: ReactNode; // 
}

// eslint-disable-next-line react/display-name
const InternalLink = React.forwardRef<HTMLAnchorElement, InternalLinkProps>((props, ref) => {
  // Peel off the onClick handler if given and the next props...
  const { href, as, onClick, ...rest } = props;

  // I defined the handler in the component here because I need access to react context ala useContext ... not demonstrated here
  const wrappedOnClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>): void => {
    if (onClick) {
      // If the consumer passed in onClick, call it
      onClick(e);
    }

    // Record analytics, close menus, etc
  };

  return (
    <NextLink {...{ href, as }}>
      <a ref={ref} {...rest} onClick={wrappedOnClick} />
    </NextLink>
  );
});

export default InternalLink;
```
The key points here:
 - Very similar signature to ExternalLink above with the same features.
 - Wraps the Next Link component thus enabling routing

# Examples
```
 // TS error - no href
<ExternalLink>Lorem</ExternalLink>
<InternalLink>Lorem</InternalLink>

// TS error - no children
<ExternalLink href="https://www.hashnode.com" />
InternalLink href="https://www.hashnode.com" />

// Success: Text Children
<ExternalLink href="https://www.hashnode.com">Lorem ipsum</ExternalLink> 
<InternalLink href="/contact">Lorem ipsum</InternalLink>

 // Success - other components
<ExternalLink href="https://www.hashnode.com"><span>Lorem ipsum</span</ExternalLink>
<InternalLink href="/contact"><span>Lorem ipsum</span></InternalLink>

 // Success - other React components
 <ExternalLink href="https://www.hashnode.com"><MailIcon ... /></ExternalLink>
<InternalLink href="/contact" as="/contact"><MailIcon /></InternalLink>

 // Success - custom onClick gets wrapped correctly
 <ExternalLink href="https://www.hashnode.com" onClick={(): void => { console.log('inline onClick called'); }}>Lorem</ExternalLink>
<InternalLink href="/contact" onClick={(): void => { console.log('inline onClick called'); }}>Lorem</InternalLink>

// Success - junk drawer of html attributes
 <ExternalLink href="https://www.hashnode.com" title="Lorem" target="_blank" rel="noopener" id="mylink">Lorem ipsum</ExternalLink>
<InternalLink href="/contact" title="Lorem" target="_blank" rel="noopener" id="mylink2">Lorem ipsum</InternalLink>

// Success Using with Material-Ui button
<Button variant="outlined" color="primary" id="b1" href="https://hashnode.com" target="_blank" component={ExternalLink}>
  External Link
</Button>
<Button variant="outlined" color="secondary" id="b2" href="/contact" component={InternalLink}>
  Internal Link
</Button>
```

Here is a screenshot of the final two examples:
![Screen Shot 2020-01-24 at 5.02.35 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1579907153841/eMFBjodBe.png)

... and the rendered markup.
![Screen Shot 2020-01-24 at 5.03.23 PM.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1579907162088/ALMEcHtsL.png)

It feels like we have a fairly complete pair of interchangeable internal and external link components that we can easily pass either to Material-UI components and achieved our other goals. Success!




## Gotchas
I'm learning TypeScript so these things were a source of confusion:

### React.HTMLAttributes vs. React.HTMLProps
When defining the props interface, extend `React.HTMLProps<HTMLAnchorElement>` rather than `React.HTMLAttributes<HTMLAnchorElement>`. The later doesn't allow the `href` attribute.  However, the former makes the `href` optional (presumably for internal page anchors) and so I added it to my type definition anyway, making the whole gotcha moot. 

### Event Types 
React wraps the native browser events in [Synthentic Event](https://reactjs.org/docs/events.html). As such, the type definition isn't the normal function definition type. React further defines these types such that our `onClick` has a type of [MouseEvent](https://reactjs.org/docs/events.html#mouse-events). This got even weirder as I was passing around the setState function from React's `useState` hook which has a type of `React.Dispatch<React.SetStateAction>` but I'll discuss that further in an different article.

### Extending Next's Link props
While [Next 9 is written in TypeScript](https://nextjs.org/blog/next-9#built-in-zero-config-typescript-support), I had a heck of a time attempting to extend the [LinkProps](https://github.com/zeit/next.js/blob/canary/packages/next/client/link.tsx#L45). Specifically, `href` and `as` are `Url`types and conflict with the React HTML types. I gave up due to time, but I'd like to revisit this. The goal would be that I could simply do:
```
type InternalLinkProps extends React.HTMLProps<HTMLAnchorElement> & NextLinkProps
```

### Forward Ref and Component Name issue
When using `forwardRef`, I was getting ESlint warnings about not having a component name for the wrapped component. I dug into it a bit, but the proper solution seemed fairly verbose for this component and I chose to suppress the warnings with `// eslint-disable-next-line react/display-name`


## Conclusion
This is my first time converting existing components utilizing Material-UI to TypeScript. If you have a better solution or find a typo, please point it out in the comments. Additionally, check out other programming articles at  [blainegarrett.com](https://www.blainegarrett.com/programming) or follow me on twitter [@blainegarrett](https://twitter.com/blainegarrett).

Image Credit: [IMG_20170829_165725](https://www.flickr.com/photos/98900394@N04/37040761535) by [leaf watoru](https://www.flickr.com/photos/98900394@N04) is licensed under [CC BY-SA 2.0](https://creativecommons.org/licenses/by-sa/2.0/?ref=ccsearch&atype=rich)
