Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 1 addition & 56 deletions src/hooks/__tests__/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,9 @@
import {renderHook} from '@testing-library/react'
import {
useMouseAndTouchTracker,
isDropdownsStateEqual,
useElementIds,
} from '../utils'
import {useMouseAndTouchTracker, isDropdownsStateEqual} from '../utils'
import {getInitialValue, getDefaultValue, getItemAndIndex} from '../utils-ts'
import {dropdownDefaultProps} from '../utils.dropdown'

describe('utils', () => {
describe('useElementIds', () => {
test('returns the same reference on re-renders when the props do not change', () => {
const getTestItemId = () => 'test-item-id'
const {result, rerender} = renderHook(useElementIds, {
initialProps: {
id: 'test-id',
labelId: 'test-label-id',
menuId: 'test-menu-id',
getItemId: getTestItemId,
toggleButtonId: 'test-toggle-button-id',
inputId: 'test-input-id',
},
})
const renderOneResult = result.current
rerender({
id: 'test-id',
labelId: 'test-label-id',
menuId: 'test-menu-id',
getItemId: getTestItemId,
toggleButtonId: 'test-toggle-button-id',
inputId: 'test-input-id',
})
const renderTwoResult = result.current
expect(renderOneResult).toBe(renderTwoResult)
})

test('returns a new reference on re-renders when the props change', () => {
const {result, rerender} = renderHook(useElementIds, {
initialProps: {
id: 'test-id',
labelId: 'test-label-id',
menuId: 'test-menu-id',
getItemId: () => 'test-item-id',
toggleButtonId: 'test-toggle-button-id',
inputId: 'test-input-id',
},
})
const renderOneResult = result.current
rerender({
id: 'test-id',
labelId: 'test-label-id',
menuId: 'test-menu-id',
getItemId: () => 'test-item-id',
toggleButtonId: 'test-toggle-button-id',
inputId: 'test-input-id',
})
const renderTwoResult = result.current
expect(renderOneResult).not.toBe(renderTwoResult)
})
})

describe('itemToString', () => {
test('returns empty string if item is falsy', () => {
const emptyString = dropdownDefaultProps.itemToString(null)
Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useCombobox/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
useGetterPropsCalledChecker,
useScrollIntoView,
useControlPropsValidator,
useElementIds,
isDropdownsStateEqual,
} from '../utils'
import {
Expand All @@ -25,6 +24,7 @@ import {
} from './utils'
import downshiftUseComboboxReducer from './reducer'
import * as stateChangeTypes from './stateChangeTypes'
import {useElementIds} from '../utils.dropdown/useElementIds'

useCombobox.stateChangeTypes = stateChangeTypes

Expand Down Expand Up @@ -114,7 +114,7 @@ function useCombobox(userProps = {}) {
const mouseAndTouchTrackers = useMouseAndTouchTracker(
environment,
handleBlurInTracker,
downshiftRefs
downshiftRefs,
)
const setGetterPropCallInfo = useGetterPropsCalledChecker(
'getInputProps',
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useSelect/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
useGetterPropsCalledChecker,
useScrollIntoView,
useControlPropsValidator,
useElementIds,
useMouseAndTouchTracker,
isDropdownsStateEqual,
} from '../utils'
Expand All @@ -27,6 +26,7 @@ import {isReactNative, isReactNativeWeb} from '../../is.macro'
import downshiftSelectReducer from './reducer'
import {defaultProps, propTypes} from './utils'
import * as stateChangeTypes from './stateChangeTypes'
import { useElementIds } from '../utils.dropdown/useElementIds'

useSelect.stateChangeTypes = stateChangeTypes

Expand Down
6 changes: 1 addition & 5 deletions src/hooks/useTagGroup/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import PropTypes from 'prop-types'

export {
type UseElementIdsProps,
type UseElementIdsReturnValue,
useElementIds,
} from './useElementIds'
export {useElementIds} from './useElementIds'
export {getInitialState} from './getInitialState'
export {isStateEqual} from './isStateEqual'
export {
Expand Down
55 changes: 31 additions & 24 deletions src/hooks/useTagGroup/utils/useElementIds.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,60 @@
import * as React from 'react'

import {generateId} from '../../../utils-ts'
import {UseTagGroupProps} from '../index.types'

export type UseElementIdsProps = Pick<
UseTagGroupProps<unknown>,
'id' | 'getTagId' | 'tagGroupId'
>
type UseElementIdsProps = {
id?: string
tagGroupId?: string
getTagId?: (index: number) => string
}

type UseElementIdsReturnValue = {
tagGroupId: string
getTagId: (index: number) => string
}

export type UseElementIdsReturnValue = Required<
Pick<UseTagGroupProps<unknown>, 'getTagId' | 'tagGroupId'>
>
// eslint-disable-next-line @typescript-eslint/dot-notation
const reactUseId = React['useId']

// istanbul ignore next
export const useElementIds: (
props: UseElementIdsProps,
) => UseElementIdsReturnValue =
'useId' in React // Avoid conditional useId call
? useElementIdsR18
: useElementIdsLegacy
typeof reactUseId === 'function' ? useElementIdsR18 : useElementIdsLegacy

function useElementIdsR18({
id,
tagGroupId,
getTagId,
}: UseElementIdsProps): UseElementIdsReturnValue {
// Avoid conditional useId call
const reactId = `downshift-${React.useId()}`
const reactId = `downshift-${reactUseId()}`
if (!id) {
id = reactId
}

const elementIdsRef = React.useRef({
tagGroupId: tagGroupId ?? `${id}-tag-group`,
getTagId: getTagId ?? (index => `${id}-tag-${index}`),
})
const elementIds = React.useMemo(
() => ({
tagGroupId: tagGroupId ?? `${id}-tag-group`,
getTagId: getTagId ?? (index => `${id}-tag-${index}`),
}),
[getTagId, id, tagGroupId],
)

return elementIdsRef.current
return elementIds
}

function useElementIdsLegacy({
id = `downshift-${generateId()}`,
getTagId,
tagGroupId,
}: UseElementIdsProps): UseElementIdsReturnValue {
const elementIdsRef = React.useRef({
tagGroupId: tagGroupId ?? `${id}-tag-group`,
getTagId: getTagId ?? (index => `${id}-tag-${index}`),
})

return elementIdsRef.current
const elementIds = React.useMemo(
() => ({
tagGroupId: tagGroupId ?? `${id}-tag-group`,
getTagId: getTagId ?? (index => `${id}-tag-${index}`),
}),
[getTagId, id, tagGroupId],
)

return elementIds
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {renderHook} from '@testing-library/react'
import {useElementIds} from '../utils'

import {useElementIds} from '../useElementIds'

jest.mock('react', () => {
const {useId, ...react} = jest.requireActual('react')
return react
})

jest.mock('../../utils-ts/generateId.ts', () => ({
jest.mock('../../../utils-ts', () => ({
generateId: jest.fn().mockReturnValue('test-id'),
}))

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {renderHook} from '@testing-library/react'
import {useElementIds} from '../utils'

import {useElementIds} from '../useElementIds'

jest.mock('react', () => {
return {
Expand Down
54 changes: 54 additions & 0 deletions src/hooks/utils.dropdown/__tests__/useElementIds.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {renderHook} from '@testing-library/react'

import {useElementIds} from '../useElementIds'

describe('useElementIds', () => {
test('returns the same reference on re-renders when the props do not change', () => {
const getTestItemId = () => 'test-item-id'
const {result, rerender} = renderHook(useElementIds, {
initialProps: {
id: 'test-id',
labelId: 'test-label-id',
menuId: 'test-menu-id',
getItemId: getTestItemId,
toggleButtonId: 'test-toggle-button-id',
inputId: 'test-input-id',
},
})
const renderOneResult = result.current
rerender({
id: 'test-id',
labelId: 'test-label-id',
menuId: 'test-menu-id',
getItemId: getTestItemId,
toggleButtonId: 'test-toggle-button-id',
inputId: 'test-input-id',
})
const renderTwoResult = result.current
expect(renderOneResult).toBe(renderTwoResult)
})

test('returns a new reference on re-renders when the props change', () => {
const {result, rerender} = renderHook(useElementIds, {
initialProps: {
id: 'test-id',
labelId: 'test-label-id',
menuId: 'test-menu-id',
getItemId: () => 'test-item-id',
toggleButtonId: 'test-toggle-button-id',
inputId: 'test-input-id',
},
})
const renderOneResult = result.current
rerender({
id: 'test-id',
labelId: 'test-label-id',
menuId: 'test-menu-id',
getItemId: () => 'test-item-id',
toggleButtonId: 'test-toggle-button-id',
inputId: 'test-input-id',
})
const renderTwoResult = result.current
expect(renderOneResult).not.toBe(renderTwoResult)
})
})
75 changes: 75 additions & 0 deletions src/hooks/utils.dropdown/useElementIds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import * as React from 'react'

import {generateId} from '../../utils-ts'

type UseElementIdsProps = {
id?: string
labelId?: string
menuId?: string
getItemId?: (index: number) => string
toggleButtonId?: string
inputId?: string
}

type UseElementIdsReturnValue = {
labelId: string
menuId: string
getItemId: (index: number) => string
toggleButtonId: string
inputId: string
}

// eslint-disable-next-line @typescript-eslint/dot-notation
const reactUseId = React['useId']

export const useElementIds =
typeof reactUseId === 'function' ? useElementIdsR18 : useElementIdsLegacy

function useElementIdsR18({
id,
labelId,
menuId,
getItemId,
toggleButtonId,
inputId,
}: UseElementIdsProps): UseElementIdsReturnValue {
const reactId = `downshift-${reactUseId()}`
if (!id) {
id = reactId
}

const elementIds = React.useMemo(
() => ({
labelId: labelId ?? `${id}-label`,
menuId: menuId ?? `${id}-menu`,
getItemId: getItemId ?? (index => `${id}-item-${index}`),
toggleButtonId: toggleButtonId ?? `${id}-toggle-button`,
inputId: inputId ?? `${id}-input`,
}),
[getItemId, id, inputId, labelId, menuId, toggleButtonId],
)

return elementIds
}

function useElementIdsLegacy({
id = `downshift-${generateId()}`,
labelId,
menuId,
getItemId,
toggleButtonId,
inputId,
}: UseElementIdsProps): UseElementIdsReturnValue {
const elementIds = React.useMemo(
() => ({
labelId: labelId ?? `${id}-label`,
menuId: menuId ?? `${id}-menu`,
getItemId: getItemId ?? (index => `${id}-item-${index}`),
toggleButtonId: toggleButtonId ?? `${id}-toggle-button`,
inputId: inputId ?? `${id}-input`,
}),
[getItemId, id, inputId, labelId, menuId, toggleButtonId],
)

return elementIds
}
Loading
Loading