Repository: kyeotic/raviger (ts)

Issue:
Allow passing ref to the Link anchor.
I'm currently using the raviger `Link` for a list of links and I'm trying to use keyboard navigation to navigate through them.
To move the focus through the links I would need to set a ref on the anchor elements, this is currently not possible.
This would be easily achieved by adding `forwardRef` on the `Link` component and then pass the ref to the anchor element.
I would really appreciate if this could be implemented. :)
 


Pull request description:
add linkRef prop to Link
Add a ref-forwarding `linkRef` optional property to `<Link>`. 

closes #36 
See [this comment](https://github.com/kyeotic/raviger/issues/36#issuecomment-556935238) for details

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9e42343..eee3185 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [1.1.0] - UNRELEASED
+### Added
+- `linkRef` prop to `<Link>`
+
 ## [1.0.0] - 2019-11-12
 ### Added
 - `useLocationChange`: similar to use `usePopState`, but uses different parameters
diff --git a/docs/content/api/Link.md b/docs/content/api/Link.md
index 4d458c3..0b813db 100644
--- a/docs/content/api/Link.md
+++ b/docs/content/api/Link.md
@@ -12,7 +12,8 @@ A React component for rendering a `<a>` that uses *history* navigation for local
 export interface LinkProps
   extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
   // Unlike normal <a>, this property is required
-  href: string
+  href: string,
+  linkRef?: React.RefObject<HTMLAnchorElement>
 }
 export const Link: React.FC<LinkProps>
 {{< /highlight >}}
@@ -27,4 +28,8 @@ This component takes all the same parameters as the built-in `<a>` tag. It's `on
 <Link href="/foo">
   go to foo
 </Link>
-{{< /highlight >}}
\ No newline at end of file
+{{< /highlight >}}
+
+## Ref Passing
+
+To pass a [React ref](https://reactjs.org/docs/refs-and-the-dom.html) to the `<Link>` DOM Node use the `linkRef` property. This will assign the ref to the internal `<a>` node. In future version this will become `ref` and use the standard [forwardRef](https://reactjs.org/docs/forwarding-refs.html#forwarding-refs-to-dom-components) API.
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 5ac8af4..4a06239 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1251,6 +1251,15 @@
         "@types/istanbul-lib-report": "*"
       }
     },
+    "@types/jest": {
+      "version": "24.0.23",
+      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.23.tgz",
+      "integrity": "sha512-L7MBvwfNpe7yVPTXLn32df/EK+AMBFAFvZrRuArGs7npEWnlziUXK+5GMIUTI4NIuwok3XibsjXCs5HxviYXjg==",
+      "dev": true,
+      "requires": {
+        "jest-diff": "^24.3.0"
+      }
+    },
     "@types/json-schema": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
diff --git a/package.json b/package.json
index 1175576..425df57 100644
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
     "@kyeotic/eslint-config": "^1.0.2",
     "@testing-library/jest-dom": "^4.0.0",
     "@testing-library/react": "^8.0.7",
+    "@types/jest": "^24.0.23",
     "babel-jest": "^24.8.0",
     "eslint": "^5.6.0",
     "eslint-plugin-jest": "^22.15.2",
diff --git a/src/Link.js b/src/Link.js
index b8c6bf7..a3a441f 100644
--- a/src/Link.js
+++ b/src/Link.js
@@ -2,9 +2,9 @@ import React, { useCallback } from 'react'
 import { navigate } from './navigate'
 import { usePath, useBasePath } from './path.js'
 
-export default function Link(props) {
+export default function Link({ href, linkRef, ...props }) {
   const basePath = useBasePath()
-  const href = getLinkHref(props.href, basePath)
+  href = getLinkHref(href, basePath)
   const onClick = useCallback(
     e => {
       try {
@@ -20,7 +20,8 @@ export default function Link(props) {
     },
     [basePath, href, props.onClick]
   )
-  return <a {...props} href={href} onClick={onClick} />
+  // TODO: 2.0 - replace linkRef with forwardRef API
+  return <a {...props} href={href} onClick={onClick} ref={linkRef} />
 }
 
 export function ActiveLink(props) {
diff --git a/types/raviger.d.ts b/types/raviger.d.ts
index 69a6945..cc07a5f 100644
--- a/types/raviger.d.ts
+++ b/types/raviger.d.ts
@@ -20,6 +20,7 @@ export function useRedirect(
 export interface LinkProps
   extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
   href: string
+  linkRef?: React.RefObject<HTMLAnchorElement>
 }
 export const Link: React.FC<LinkProps>
 export interface ActiveLinkProps extends LinkProps {


Test patch:
diff --git a/test/Link.spec.js b/test/Link.spec.js
index bd13090..607ab06 100644
--- a/test/Link.spec.js
+++ b/test/Link.spec.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useRef } from 'react'
 import { render, act, fireEvent } from '@testing-library/react'
 import { Link, ActiveLink, navigate } from '../src/main.js'
 
@@ -37,6 +37,28 @@ describe('Link', () => {
     act(() => void fireEvent.click(getByTestId('link')))
     expect(document.location.pathname).toEqual('/')
   })
+  test('passes linkRef to anchor element', async () => {
+    act(() => navigate('/'))
+
+    let ref
+    function LinkTest() {
+      const linkRef = useRef()
+      ref = linkRef
+      return (
+        <Link
+          href="/foo"
+          target="_blank"
+          data-testid="linkref"
+          linkRef={linkRef}
+        >
+          go to foo
+        </Link>
+      )
+    }
+    const { getByTestId } = render(<LinkTest />)
+    expect(getByTestId('linkref')).toBe(ref.current)
+    expect(ref.current).toBeInstanceOf(HTMLAnchorElement)
+  })
 })
 describe('ActiveLink', () => {
   test('adds active class when active', async () => {