Skip to content

Commit c2f79b4

Browse files
authored
fix: update element ids when related props change (#1656)
We used to memoize the element id props on the first render and then use those throughout the lifecycle of the component. This could cause ids in the markup to be out of sync from the ids provided via props. This change updates the internal element id getter references whenever any id related props change to propagate those props changes to the markup.
1 parent 0d0ecc5 commit c2f79b4

File tree

2 files changed

+76
-17
lines changed

2 files changed

+76
-17
lines changed

src/hooks/__tests__/utils.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,61 @@ import {
66
useMouseAndTouchTracker,
77
getItemAndIndex,
88
isDropdownsStateEqual,
9+
useElementIds,
910
} from '../utils'
1011

1112
describe('utils', () => {
13+
describe('useElementIds', () => {
14+
test('returns the same reference on re-renders when the props do not change', () => {
15+
const getTestItemId = () => 'test-item-id'
16+
const {result, rerender} = renderHook(useElementIds, {
17+
initialProps: {
18+
id: 'test-id',
19+
labelId: 'test-label-id',
20+
menuId: 'test-menu-id',
21+
getItemId: getTestItemId,
22+
toggleButtonId: 'test-toggle-button-id',
23+
inputId: 'test-input-id',
24+
},
25+
})
26+
const renderOneResult = result.current
27+
rerender({
28+
id: 'test-id',
29+
labelId: 'test-label-id',
30+
menuId: 'test-menu-id',
31+
getItemId: getTestItemId,
32+
toggleButtonId: 'test-toggle-button-id',
33+
inputId: 'test-input-id',
34+
})
35+
const renderTwoResult = result.current
36+
expect(renderOneResult).toBe(renderTwoResult)
37+
})
38+
39+
test('returns a new reference on re-renders when the props change', () => {
40+
const {result, rerender} = renderHook(useElementIds, {
41+
initialProps: {
42+
id: 'test-id',
43+
labelId: 'test-label-id',
44+
menuId: 'test-menu-id',
45+
getItemId: () => 'test-item-id',
46+
toggleButtonId: 'test-toggle-button-id',
47+
inputId: 'test-input-id',
48+
},
49+
})
50+
const renderOneResult = result.current
51+
rerender({
52+
id: 'test-id',
53+
labelId: 'test-label-id',
54+
menuId: 'test-menu-id',
55+
getItemId: () => 'test-item-id',
56+
toggleButtonId: 'test-toggle-button-id',
57+
inputId: 'test-input-id',
58+
})
59+
const renderTwoResult = result.current
60+
expect(renderOneResult).not.toBe(renderTwoResult)
61+
})
62+
})
63+
1264
describe('itemToString', () => {
1365
test('returns empty string if item is falsy', () => {
1466
const emptyString = defaultProps.itemToString(null)

src/hooks/utils.js

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, {
44
useReducer,
55
useEffect,
66
useLayoutEffect,
7+
useMemo,
78
} from 'react'
89
import PropTypes from 'prop-types'
910
import {isReactNative} from '../is.macro'
@@ -97,15 +98,18 @@ const useElementIds =
9798
id = reactId
9899
}
99100

100-
const elementIdsRef = useRef({
101-
labelId: labelId || `${id}-label`,
102-
menuId: menuId || `${id}-menu`,
103-
getItemId: getItemId || (index => `${id}-item-${index}`),
104-
toggleButtonId: toggleButtonId || `${id}-toggle-button`,
105-
inputId: inputId || `${id}-input`,
106-
})
101+
const elementIds = useMemo(
102+
() => ({
103+
labelId: labelId || `${id}-label`,
104+
menuId: menuId || `${id}-menu`,
105+
getItemId: getItemId || (index => `${id}-item-${index}`),
106+
toggleButtonId: toggleButtonId || `${id}-toggle-button`,
107+
inputId: inputId || `${id}-input`,
108+
}),
109+
[getItemId, id, inputId, labelId, menuId, toggleButtonId],
110+
)
107111

108-
return elementIdsRef.current
112+
return elementIds
109113
}
110114
: function useElementIds({
111115
id = `downshift-${generateId()}`,
@@ -115,15 +119,18 @@ const useElementIds =
115119
toggleButtonId,
116120
inputId,
117121
}) {
118-
const elementIdsRef = useRef({
119-
labelId: labelId || `${id}-label`,
120-
menuId: menuId || `${id}-menu`,
121-
getItemId: getItemId || (index => `${id}-item-${index}`),
122-
toggleButtonId: toggleButtonId || `${id}-toggle-button`,
123-
inputId: inputId || `${id}-input`,
124-
})
125-
126-
return elementIdsRef.current
122+
const elementIds = useMemo(
123+
() => ({
124+
labelId: labelId || `${id}-label`,
125+
menuId: menuId || `${id}-menu`,
126+
getItemId: getItemId || (index => `${id}-item-${index}`),
127+
toggleButtonId: toggleButtonId || `${id}-toggle-button`,
128+
inputId: inputId || `${id}-input`,
129+
}),
130+
[getItemId, id, inputId, labelId, menuId, toggleButtonId],
131+
)
132+
133+
return elementIds
127134
}
128135

129136
function getItemAndIndex(itemProp, indexProp, items, errorMessage) {

0 commit comments

Comments
 (0)