Derek Chan

React Hacks 01: Reload-Free Anchor Tags

It's not an uncommon peeve for a single-page app in React to make browsers refresh upon clicking on a link, regardless of where it leads. This happens often in WordPress themes made with React, and many MERN stack apps as well. Contrary to many of the answers I've come across, there seems to be a workaround, assuming you're using React Router and the React Hooks API.

My solution involves preventing the default behavior of anchor tags and pushing to the browser history object, therefore access to the history object needs to be provided to the tags. My initial solution involved prop-drilling, the most common solution to which is to use a context, as follows:

GeneralContext.js

import React, { useState, createContext } from 'react';
let initialState = ({ history }) => ({ history });
const GeneralContext = createContext(initialState);

export default GeneralContext;

const { Provider } = GeneralContext; export const GeneralContextProvider = ({ children, initialVals }) => {
  let iState = Object.assign({}, initialState(initialVals)), [generalState, setGeneralState] = useState(iState);
  return <Provider value={{ generalState, setGeneralState }}>{children}</Provider>;
};

Wherever in the code you specify your routes, create a functional component that returns a Route with the GeneralContextProvider and the component nested inside, then refactor accordingly:

import React from 'react'; import { Route } from 'react-router-dom';

let GeneralRoute = ({ component: Component, ...rest }) => <Route exact {...rest} component={({ history }) => (
<GeneralContextProvider initialVals={{ history, match }}>
      <Component /></GeneralContextProvider>
)} />;

As you can see, the GeneralContext is provided the browser history object by means of the Route component attribute. Anything in this attribute will be passed an object with a location, history, and match attributes.

Now, for the actual link itself (don't mind the use of styled-components):

import React, { useContext } from 'react';
import GeneralContext from './GeneralContext';

function SamePageAnchor({ children, href, target, className, id, style, component }) {
  let { generalState, setGeneralState } = useContext(GeneralContext), Anchor = component || styled.a``, AlreadyOn = styled.span` text-decoration: underline; font-weight: 900; margin: 0; width: fit-content; height: fit-content; `;

  function handleClick(event) {
    if (href.startsWith('/')) {
      let newState = Object.assign({}, generalState);
      event.preventDefault();
      newState.history.push(href);
      setGeneralState(newState);
    }
  }

  return (generalState.history && generalState.history.location.pathname !== href) ? <Anchor {...{ href, target, className, id, style }} onClick={handleClick}>{children}</Anchor> : <AlreadyOn>{children}</AlreadyOn>;
}

export default SamePageAnchor;

Now you should be good to go. Make sure that wherever in your code used, it has access to a GeneralContext.

,