/ src / components / link.jsx
link.jsx
 1  import { forwardRef } from 'preact/compat';
 2  import { useLocation } from 'react-router-dom';
 3  
 4  import states from '../utils/states';
 5  
 6  /* NOTES
 7     =====
 8     Initially this uses <NavLink> from react-router-dom, but it doesn't work:
 9     1. It interferes with nested <a> inside <a> and it's difficult to preventDefault/stopPropagation from the nested <a>
10     2. isActive doesn't work properly with the weird routes that's set up in this app, due to the faux "location" to make the modals work and prevent unmounting
11     3. Not using <Link state/> because it modifies history.state that *persists* across page reloads. I don't need that, so using valtio's states instead.
12  */
13  
14  const Link = forwardRef((props, ref) => {
15    let routerLocation;
16    try {
17      routerLocation = useLocation();
18    } catch (e) {}
19    let hash = (location.hash || '').replace(/^#/, '').trim();
20    if (hash === '') hash = '/';
21    const { to, ...restProps } = props;
22  
23    // Handle encodeURIComponent of searchParams values
24    if (!!hash && hash !== '/' && hash.includes('?')) {
25      const parsedHash = new URL(hash, location.origin); // Fake base URL
26      if (parsedHash.searchParams.size) {
27        const searchParamsStr = Array.from(parsedHash.searchParams.entries())
28          .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
29          .join('&');
30        hash = parsedHash.pathname + '?' + searchParamsStr;
31      }
32    }
33  
34    const isActive = hash === to || decodeURIComponent(hash) === to;
35    return (
36      <a
37        ref={ref}
38        href={`#${to}`}
39        {...restProps}
40        class={`${props.class || ''} ${isActive ? 'is-active' : ''}`}
41        onClick={(e) => {
42          if (e.currentTarget?.parentNode?.closest('a')) {
43            // If this <a> is nested inside another <a>
44            e.stopPropagation();
45          }
46          if (routerLocation) states.prevLocation = routerLocation;
47          props.onClick?.(e);
48        }}
49      />
50    );
51  });
52  
53  export default Link;