Creating a “better” link in react

Update – 2020/01/30

I posted this blog on /r/webdev and the reddit webdev community being the wealth of information it is, I received some awesome feedback.

By default I had just been starting each component from a skeleton of a class based component, I was aware that functional components were available but rarely swayed from the class based structure I’ve normally used.

Card

I will just be posting the updated final code here, if you want to view the steps to how I got to the final product for further understanding, or what the if statement or regexp does please read past this snippet through “The Problem” and “The Implementation”

function BetterLink(props) {
  if (new RegExp("([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?").test(props.link)) {
    return (
      <a
        href={props.to}
        {...props}
      />
    );
  } else {
    return (
      <Link {...props} />
    );
  }
}

As you can see with the updated solution, we refactor the code combining the isExternal statement into the function BetterLink with the props passed in, returning the correct link we require within the if statement.

The Problem

I was recently creating a react web app that had fairly standard pods containing an image and a read more link. This link would be either a normal web anchor link going to an external URL such as (http://example.com) using an <a> tag or use the <Link> react router component included via react-router-dom to go to an internal route (/articles/how-to-set-up-an-example).

The issue here is, when the API for my CMS returns the data for these pods, we end up with no distinction between what is an external link or internal route, like below:

[
  {
    "img": "https://placeimg.com/440/330/any",
    "title": "Test Article One",
    "link": "/article/test-article-one"
  },
  {
    "img": "https://placeimg.com/440/330/any",
    "title": "Test Article Two",
    "link": "https://guestarticlesite.com/post/45"
  }
]

One option here would be to tickle this data from the API before we pump it into our own pods component, but I thought this might be something I will want to re-use in future, so leveraging reacts modular nature I set about to create a component that was a “Better Link” (see conclusion).

The Implementation

This guide will show code blocks that are evolving as such, each block replaces the last block until the snippet is complete.

1. First of all we create the component skeleton

import React, { Component } from 'react';

export default class BetterLink extends Component {

  render() {
    <>
      Here we will remove this and build a better futu... link
    </>
  }
}

What this does is create file that we can import wherever we need it creating a component we can call with <BetterLink />

The location of this file in the project structure doesn’t really matter, I usually place low level components like this in a directory alongside all my visual elements. For example src/components/BetterLink.js

2. Now we need a helper function

import React, { Component } from 'react';

const isExternal = (link) => {
  if (new RegExp("([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?").test(link)) {
    return false;
  } else {
    return true;
  }
};

export default class BetterLink extends Component {
  render() {
    <>
      Here we will remove this and build a better futu... link
    </>
  }
}

This helper function isExternal is defined as a const at the top of the file so our component can run the function when needed. The Function is fairly simple but we are using quite a complex Regular Expression to check if the string we pass the function is a URL complete with protocol or just a relative route path.

Regular expressions are a whole other subject. So for this we will just skim over it, knowing that the long string within the new RegExp() object is working to rule out that the string passed in as link to the function is not a standard web anchor link.

If the argument is a relative route link it will return false, if it is an absolute anchor link, it will return true

3. Returning the better’er link

import React, { Component } from 'react';
import { Link } from 'react-router-dom';

const isExternal = (link) => {
  if (new RegExp("([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?").test(link)) {
    return false;
  } else {
    return true;
  }
};

export default class BetterLink extends Component {

  render() {
    return isExternal(this.props.to) ?
      <a
        href={this.props.to}
        {...this.props}
      />
      :
      <Link {...this.props} />;
  }
}

Here we remove the dummy return text from the BetterLink class render function and create a statement that shows the correct link for for what we input to the Component.

Note: we have also imported the Link component from react-router-dom at the top of the code now in case the BetterLink Component is passed a route rather than a URL.

For the anchor link, we make sure to pass the component property to into the anchor element href= attribute. Any other properties that we pass will be inserted on a like for like basis, we’ll show an example below.

Example usage

All of the below examples we will use the following import (with updated path)

import BetterLink from '../../components/BetterLink';

Example 1 – React route

<BetterLink to="/blog-posts/second-blog-post">Read More</BetterLink>

The output of this may look confusing as its technically an anchor link but don’t worry React has worked its magic on the link and will do all of the routing code including history management.

<a href="/houseshares" to="/houseshares">Read More</a>

Example 2 – Anchor link

<BetterLink to="https://anotherblog.com/posts/established-blog">Read More</BetterLink>

The output of this will look a lot more like a traditional anchor link.

<a href="https://anotherblog.com/posts/established-blog">Read More</a>

This will literally direct the browser to https://anotherblog.com/posts/established-blog and ignore any internal routing.

Example 3 – Passing props

<BetterLink 
  to="https://anotherblog.com/posts/established-blog"
  className="post_read_more_link"
  target="_blank"
>
  Read More
</BetterLink>

Because of how we’ve set up our component, it will use the property to in which to decide what link we return, but it will also insert the rest of any other properties we throw at it, using the ES6 rest operator, again this is another rabit hole so we wont go down it but save that for another day.

For the example above we’ve given the component a className and a target attribute which will be passed as properties. What we will get out of the other end once react compiles is:

<a href="https://anotherblog.com/posts/established-blog" class="post_read_more_link" target="_blank">Read More</a>

Conclusion

Above, we’ve created a glorified switch which detects the link input. If its a react route it uses the react <Link> if not it defaults to a html <a> tag.

Its not too difficult to manually achieve this, which is probably why its not documented much. But as this is an issue I’ve hit a few times I thought I’d make a bit of a walk through of how to implement it for re-usability.

I am aware using this method, if there is a lack of validation at the CMS end there may be the potential to malform links, this is why I call this method “better” links, not perfect links.

📷 chain “links”

Photo by Mike Alonzo on Unsplash
Creating a “better” link in react
Scroll to top