DEV Community

Gustavo Santos
Gustavo Santos

Posted on

Refactoring React: Manage page paths through path functions

In a React web application, it is common to have redirections between pages. And it is common to have React Components that build the URL path pointing to some other page, outside its context. Such as the following example:

// a component used across the app

import { settingsRoute } from 'app/routes'

export cons OrderDescription = () => {
  const order = useOrder()

  return (
    <ul>
      {order.products.map(product => (
        <li key={product.sku}>
          <Link href={`/collections/${product.collectionId}/products/${product.id}`}>
            {product.name}
          </Link>
        </li>
      )}
    </ul>
  )
}
Enter fullscreen mode Exit fullscreen mode

In this case, the OrderDescription component is building the path to the product page and passing as value to Link's
href property.

On the other hand, the product page received both the collection identifier and product identifier from the path.

// /pages/product.js

export const ProductPage = () => {
  const { collectionId, productId } = useParams()
  const product = useProduct(collectionId, productId)

  return (
    <div />
  )
}
Enter fullscreen mode Exit fullscreen mode

The problem here is that OrderDescription needs to know how to build the URL path to the ProductPage component. In fact, any page that create a redirection link to the product page will need to know how to build the path to this page.

This kind of smell is called Shotgun Surgery. It happens when the same knowledge is placed between different locations through the application, where each update requires changing the knowledge spread across the source code.

With this example, if the parameters of a Product Page need to change, every place that creates a link to a product page will have to change.

One way of dealing with this smell is by creating a class or a function that encapsulates this knowledge of building links for products.

The first step is to choose the abstraction. In this post, I'll be using a function to build the page path.

// /pages/product.js

export const productPath = product =>
`/collections/${product.collectionId}/products/${product.id}`

export const ProductPage = () => {
  const { collectionId, productId } = useParams()
  const product = useProduct(collectionId, productId)

  return (
    <div />
  )
}
Enter fullscreen mode Exit fullscreen mode

Now we can update every place that builds the product page path and replace them by calling the function productPath passing the product as argument.

export cons OrderDescription = () => {
  const order = useOrder()

  return (
    <ul>
      {order.products.map(product => (
        <li key={product.sku}>
          <Link href={productPath(product)}>
            {product.name}
          </Link>
        </li>
      )}
    </ul>
  )
}
Enter fullscreen mode Exit fullscreen mode

Remember to be careful and keep the tests running while making the refactoring. It's important to not make behavior changes during a refactoring. If everything is green, commit the code.

Conclusion

By using path functions, we can encapsulate the behavior of creating path links based on external parameters. We leverage on the consumer of those path parameters to describe how to build the path to that page and by doing this, we avoid knowledge leaking across the application.

Even if there is only one place that builds a reference to a page through a URL path, I'd suggest doing this refactoring because reading the function call is way easier for the reader to understand what is going on than building and interpolating strings mentally.

Top comments (0)