ReactJS - Add slug to url (dynamic slug in frontend)

Viewed 464

I'm trying to create something like here on stackoverflow:

When I enter this URL:

https://stackoverflow.com/questions/70615440

it will update the url to:

https://stackoverflow.com/questions/70615440/refactor-switch-statement-to-reduce-code-complexity

The url is possible to visit by id. It will add / update the slug to the url. I can also use the a random slug https://stackoverflow.com/questions/70615440/xxx and it will update it to the correct one :https://stackoverflow.com/questions/70615440/refactor-switch-statement-to-reduce-code-complexity

xenForo does something similar:

https://xenforo.com/community/threads/200874/
https://xenforo.com/community/threads/xenforo-2-2-8-patch-1-and-add-on-updates-released.200874/

How can I achieve something like this in a React app? How is that behavior called?

3 Answers

It's called routing. You can install react-router-dom and use useHistory and useLocation hooks. The usage is very simple. From documentation about useLocation:

The useLocation hook returns the location object that represents the current URL.

You can save the current URL in pathname and compare it with the id, if they match, you can use useHistory for navigation.

Read more

Example code:

const { pathname } = useLocation();
const history = useHistory();
const handleChange = () => history.push("/questions/70615440/refactor-switch-statement-to-reduce-code-complexity");

useEffect(() => {
    if(pathname === "/questions/70615440"){handleChange()}
  }, []);

To achieve this kind of routing, I have implemented two routes with react-router-dom. So in App.js:

<Route
  exact={true}
  path="/products/:id/:slug"
  component={ProductPage}
/>
<Route
  exact={true}
  path="/products/:id"
  component={ProductPageWrapper}
/>

In this case, if the slug was present, it would go directly to ProductPage, but if it was not present, it would fall into the the ProdutctPageWrapper which is a redirect to ProductPage. So in ProductPageWrapper.js

import slugify from 'slugify';
import { Redirect, useParams, useHistory } from 'react-router-dom';
import { fetchItem } from 'utils/Fetchers';

function ProductPageWrapper({ children, ...rest }) {
  const [productSlug, setProductSlug] = useState('');
  const params = useParams();
  const history = useHistory();
  useEffect(() => {
    // Get the slug from where you stored it or set it so you can use it in the 
    // Redirect. I use slugify to create slug, but you can do it however you prefer
    fetchItem('/api/products/' + params.id, function (err, product) {
      if (err) {
        history.push('/error-page');
      } else {
        setProductSlug(
          slugify(product.title, {
            lower: true,
            strict: true,
            locale: 'en';,
          }),
        );
      }
    });
  }, []);

  return (
    <Redirect
      to={{
        pathname: `/products/${params.id}/${productSlug}`,
      }}
    />
  );
}

In the ProductPage.js I also check if the slug is correct, if not I use history.replace:

import { useHistory, useParams } from 'react-router-dom';

function ProductPage (props) {
  const { slug, id } = useParams();
  const history = useHistory();
    useEffect(() => {
    // Get the product info and check if the slug is correct
    fetchItem('/api/products/' + params.id, function (err, product) {
      if (err) {
        history.push('/error-page');
      } else {
        if (slug !== product.slug) history.replace(product.slug);
        // do what you need with the data
      }
    });
  }, []);
  // ... rest of the component
}

Here is working example I have created with useEffect only. You can refer this Click here It will work for both http://example.com/product/:id as well as http://example.com/product/:id/:slug

BrowserRouter Section from App.js

<BrowserRouter>
    <Routes>
        <Route exact={true} path="/products/:id" element={<ProductPage />} />
        <Route
            exact={true}
            path="/products/:id/:slug"
            element={<ProductPage />}
        />
    </Routes>
</BrowserRouter>

ProductPage.js

import { useNavigate, Link, useParams } from "react-router-dom";
import { useEffect, useCallback } from "react";

const ProductPage = (props) => {
    const navigate = useNavigate();
    const { id, slug } = useParams();

    const handleRedirection = useCallback(() => {
        // Get id and slug from asynchronous calls
        // Assuming below object returned from API
        
        const response = { id: 1, slug: "slug1" };

        if (id === response .id && slug === response .slug) {
          // Don't do anything as the user is on right page
        } else if (id === response .id && slug !== response .slug) {
            navigate("/products/" + response .id + "/" + response .slug);
        } else {
            console.log("ID doesn't exist on server");
        }
    }, [id, slug, navigate]);

    useEffect(() => {
        handleRedirection();
    }, [handleRedirection]);

    return <Link to="/">Go to Home</Link>;
};

export default ProductPage;
Related