Skip to content

Instantly share code, notes, and snippets.

@lebbe
Last active January 13, 2023 15:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lebbe/da4ba34af1e3c47f9b7bc9ebc507c351 to your computer and use it in GitHub Desktop.
Save lebbe/da4ba34af1e3c47f9b7bc9ebc507c351 to your computer and use it in GitHub Desktop.
Open link in new window via a hook
/**
* 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;
}
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