Last active
January 13, 2023 15:56
-
-
Save lebbe/da4ba34af1e3c47f9b7bc9ebc507c351 to your computer and use it in GitHub Desktop.
Open link in new window via a hook
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* If you have links you want to open in a new browser window, but you are not | |
* able to customize the link. Wrap a div around the group of links, and add | |
* the returned ref as its property. | |
* | |
* If you have clickable elements within the link that should not be handled as | |
* a navigation, you should put this property on the element: | |
* | |
* ``` | |
* onClickCapture={(e) => e.preventDefault()} | |
* ``` | |
*/ | |
import { useEffect, useRef } from 'react'; | |
function handler(e: MouseEvent) { | |
if (e.defaultPrevented) return; | |
// These type assertions are safe: We either end up with an actual Anchor or | |
// we have null or undefined, which returns on the next statement/line. | |
const A = (e.target as HTMLElement)?.closest('A') as HTMLAnchorElement; | |
if (!A) return; | |
e.preventDefault(); | |
window.open(A.href, '_blank'); | |
} | |
export default function useOpenLinksInNewWindow() { | |
const ref = useRef<HTMLDivElement>(null); | |
useEffect(function () { | |
const div = ref.current; | |
if (!div) return; | |
div.addEventListener('click', handler); | |
return function () { | |
div.removeEventListener('click', handler); | |
}; | |
}, []); | |
return ref; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { render, screen } from '@testing-library/react'; | |
import userEvent from '@testing-library/user-event'; | |
import useOpenLinksInNewWindow from './useOpenLinksInNewWindow'; | |
function Dummy() { | |
return ( | |
<> | |
<div ref={useOpenLinksInNewWindow()}> | |
<a href="http://test.one.com">Test 1</a> | |
<a href="http://test.two.com"> | |
Test <span data-testid="heisann">Hello</span> | |
<button onClickCapture={(e) => e.preventDefault()}>Button inside link</button>2 | |
</a> | |
<button>Button outside of links</button> | |
</div> | |
<a href="http://test.three.com" onClick={(e) => e.preventDefault()}> | |
Link outside of div | |
</a> | |
</> | |
); | |
} | |
describe('useOpenLinksInNewWindow', () => { | |
it('should open link in new window via window.open', async () => { | |
const spyWindowOpen = jest.spyOn(window, 'open'); | |
spyWindowOpen.mockImplementation(jest.fn()); | |
render(<Dummy />); | |
await userEvent.click(screen.getByText('Test 1')); | |
expect(spyWindowOpen).toHaveBeenCalledTimes(1); | |
expect(spyWindowOpen).toHaveBeenCalledWith('http://test.one.com/', '_blank'); | |
await userEvent.click(screen.getByTestId('heisann')); | |
expect(spyWindowOpen).toHaveBeenCalledTimes(2); | |
expect(spyWindowOpen).toHaveBeenCalledWith('http://test.two.com/', '_blank'); | |
// Should not affect links or other elements outside of div | |
await userEvent.click(screen.getByText('Link outside of div')); | |
expect(spyWindowOpen).toHaveBeenCalledTimes(2); | |
await userEvent.click(screen.getByText('Button outside of links')); | |
expect(spyWindowOpen).toHaveBeenCalledTimes(2); | |
// And not elements inside of link, if it is properly preventing default. | |
await userEvent.click(screen.getByText('Button inside link')); | |
expect(spyWindowOpen).toHaveBeenCalledTimes(2); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment