diff --git a/.changeset/spotty-parents-beam.md b/.changeset/spotty-parents-beam.md new file mode 100644 index 00000000000..af36538b516 --- /dev/null +++ b/.changeset/spotty-parents-beam.md @@ -0,0 +1,5 @@ +--- +"@primer/components": minor +--- + +Adds new `useSafeTimeout` helper Hook diff --git a/docs/content/useSafeTimeout.mdx b/docs/content/useSafeTimeout.mdx new file mode 100644 index 00000000000..c0c6ece8882 --- /dev/null +++ b/docs/content/useSafeTimeout.mdx @@ -0,0 +1,32 @@ +--- +title: useSafeTimeout +--- + +`useSafeTimeout` is a utility Hook that allows you to safely call `setTimeout` and `clearTimeout` within a component, ensuring that all timeouts are cleared when the component unmounts. + + +### Usage + +```jsx live + + {([]) => { + const {safeSetTimeout, safeClearTimeout} = useSafeTimeout() + let timeoutId = null + + const handleOnClick = () => { + timeoutId = safeSetTimeout(() => window.alert('hello!'), 5000) + } + + const cancelTimeout = () => { + safeClearTimeout(timeoutId) + } + + return ( + <> + + + + ) + }} + +``` diff --git a/docs/src/@primer/gatsby-theme-doctocat/nav.yml b/docs/src/@primer/gatsby-theme-doctocat/nav.yml index b133699c444..9a5ab38a2b6 100644 --- a/docs/src/@primer/gatsby-theme-doctocat/nav.yml +++ b/docs/src/@primer/gatsby-theme-doctocat/nav.yml @@ -18,7 +18,10 @@ - title: Overriding Styles url: /overriding-styles - +- title: Hooks + children: + - title: useSafeTimeout + url: /useSafeTimeout - title: Components children: - title: Avatar diff --git a/index.d.ts b/index.d.ts index 21fff14e7dd..8813251a49f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -100,6 +100,11 @@ declare module '@primer/components' { setOpen: (open: boolean) => void } + export const useSafeTimeout: () => { + safeSetTimeout: typeof window.setTimeout, + safeClearTimeout: typeof window.clearTimeout + } + export const useMouseIntent: () => void export interface ButtonProps @@ -663,6 +668,11 @@ declare module '@primer/components/lib/hooks/useMouseIntent' { export default useMouseIntent } +declare module '@primer/components/lib/hooks/useMouseSafeTimeout' { + import {useSafeTimeout} from '@primer/components' + export default useSafeTimeout +} + declare module '@primer/components/lib/BaseStyles' { import {BaseStyles} from '@primer/components' export default BaseStyles diff --git a/package.json b/package.json index fd43138d03f..ff2a58a4e3f 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,9 @@ "@storybook/react": "^6.1.17", "@testing-library/dom": "7.29.0", "@testing-library/react": "11.2.2", + "@testing-library/react-hooks": "5.0.3", "@testing-library/user-event": "12.6.0", + "@types/enzyme": "3.10.8", "@types/jest": "26.0.20", "@types/jest-axe": "3.5.1", "@typescript-eslint/eslint-plugin": "4.14.1", diff --git a/src/FilterList.tsx b/src/FilterList.tsx index 614f42361b0..b77824f7f5a 100644 --- a/src/FilterList.tsx +++ b/src/FilterList.tsx @@ -94,7 +94,7 @@ FilterListItem.propTypes = { as: PropTypes.oneOfType([PropTypes.string, PropTypes.elementType]), children: PropTypes.node, className: PropTypes.string, - count: PropTypes.string, + count: PropTypes.number, selected: PropTypes.bool, theme: PropTypes.object, ...COMMON.propTypes, diff --git a/src/__tests__/Avatar.tsx b/src/__tests__/Avatar.tsx index 1df8968ef25..3f266f8be5a 100644 --- a/src/__tests__/Avatar.tsx +++ b/src/__tests__/Avatar.tsx @@ -9,7 +9,7 @@ import {COMMON} from '../constants' expect.extend(toHaveNoViolations) describe('Avatar', () => { - behavesAsComponent(Avatar, [COMMON]) + behavesAsComponent({Component: Avatar, systemPropArray: [COMMON]}) checkExports('Avatar', { default: Avatar diff --git a/src/__tests__/AvatarStack.tsx b/src/__tests__/AvatarStack.tsx index da895eaacbe..d0ddd883322 100644 --- a/src/__tests__/AvatarStack.tsx +++ b/src/__tests__/AvatarStack.tsx @@ -26,7 +26,7 @@ const rightAvatarComp = ( ) describe('Avatar', () => { - behavesAsComponent(AvatarStack, [COMMON], () => avatarComp) + behavesAsComponent({Component: AvatarStack, systemPropArray: [COMMON], toRender: () => avatarComp}) checkExports('AvatarStack', { default: AvatarStack diff --git a/src/__tests__/BorderBox.tsx b/src/__tests__/BorderBox.tsx index 4fe8682d2ea..e546cdd4d13 100644 --- a/src/__tests__/BorderBox.tsx +++ b/src/__tests__/BorderBox.tsx @@ -9,7 +9,7 @@ import 'babel-polyfill' expect.extend(toHaveNoViolations) describe('BorderBox', () => { - behavesAsComponent(BorderBox, [LAYOUT, COMMON, BORDER, FLEX]) + behavesAsComponent({Component: BorderBox, systemPropArray: [LAYOUT, COMMON, BORDER, FLEX]}) checkExports('BorderBox', { default: BorderBox diff --git a/src/__tests__/Box.tsx b/src/__tests__/Box.tsx index 9ee31478c8a..306e737eb4f 100644 --- a/src/__tests__/Box.tsx +++ b/src/__tests__/Box.tsx @@ -9,7 +9,7 @@ import {behavesAsComponent, checkExports, render} from '../utils/testing' expect.extend(toHaveNoViolations) describe('Box', () => { - behavesAsComponent(Box, [COMMON, LAYOUT, FLEX]) + behavesAsComponent({Component: Box, systemPropArray: [COMMON, LAYOUT, FLEX]}) checkExports('Box', { default: Box, diff --git a/src/__tests__/BranchName.tsx b/src/__tests__/BranchName.tsx index 441d579d88c..6a1f6022f70 100644 --- a/src/__tests__/BranchName.tsx +++ b/src/__tests__/BranchName.tsx @@ -8,7 +8,7 @@ import 'babel-polyfill' expect.extend(toHaveNoViolations) describe('BranchName', () => { - behavesAsComponent(BranchName, [COMMON]) + behavesAsComponent({Component: BranchName, systemPropArray: [COMMON]}) checkExports('BranchName', { default: BranchName diff --git a/src/__tests__/Breadcrumb.tsx b/src/__tests__/Breadcrumb.tsx index 13f2ea8e9db..e5d09e92e33 100644 --- a/src/__tests__/Breadcrumb.tsx +++ b/src/__tests__/Breadcrumb.tsx @@ -8,7 +8,7 @@ import 'babel-polyfill' expect.extend(toHaveNoViolations) describe('Breadcrumb', () => { - behavesAsComponent(Breadcrumb, [COMMON]) + behavesAsComponent({Component: Breadcrumb, systemPropArray: [COMMON]}) checkExports('Breadcrumb', { default: Breadcrumb @@ -28,12 +28,4 @@ describe('Breadcrumb', () => { it('adds the Breadcrumb class', () => { expect(rendersClass(, 'Breadcrumb')).toEqual(true) }) - - it('wraps its children in an li', () => { - const children = yo - const wrapper = mount({children}) - const list = wrapper.find('ol') - expect(list.exists()).toEqual(true) - expect(render(list.childAt(0)).type).toEqual('li') - }) }) diff --git a/src/__tests__/BreadcrumbItem.tsx b/src/__tests__/BreadcrumbItem.tsx index 0630d5efb70..5109642b54d 100644 --- a/src/__tests__/BreadcrumbItem.tsx +++ b/src/__tests__/BreadcrumbItem.tsx @@ -8,7 +8,7 @@ import {behavesAsComponent, render} from '../utils/testing' expect.extend(toHaveNoViolations) describe('Breadcrumb.Item', () => { - behavesAsComponent(Breadcrumb.Item, [COMMON]) + behavesAsComponent({Component: Breadcrumb.Item, systemPropArray:[COMMON]}) it('renders an by default', () => { expect(render().type).toEqual('a') diff --git a/src/__tests__/Button.tsx b/src/__tests__/Button.tsx index 4c709efbf93..75110a095d9 100644 --- a/src/__tests__/Button.tsx +++ b/src/__tests__/Button.tsx @@ -20,7 +20,7 @@ expect.extend(toHaveNoViolations) function noop() {} describe('Button', () => { - behavesAsComponent(Button, [COMMON, LAYOUT]) + behavesAsComponent({Component: Button, systemPropArray: [COMMON, LAYOUT]}) checkExports('Button', { default: Button, @@ -69,7 +69,7 @@ describe('Button', () => { }) describe('ButtonPrimary', () => { - behavesAsComponent(ButtonPrimary, [COMMON, LAYOUT]) + behavesAsComponent({Component: ButtonPrimary, systemPropArray: [COMMON, LAYOUT]}) it('renders a