Skip to content

Commit 129d60c

Browse files
Stabilize selector updater callback
Co-authored-by: Neil Raina <makeitraina@users.noreply.github.com>
1 parent f0aa5bc commit 129d60c

3 files changed

Lines changed: 75 additions & 4 deletions

File tree

src/app/configure-tasks-app/ui/TemplateSidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const TemplateSidebar = ({
4848
const currentWorkflowState = workflowStates.find((el) => el?.id === currentTask?.workflowStateId)
4949
updateStatusValue(currentWorkflowState)
5050
}
51-
}, [activeTemplate, workflowStates])
51+
}, [activeTemplate, workflowStates, updateStatusValue])
5252

5353
const windowWidth = useWindowWidth()
5454
const isMobile = windowWidth < 800 && windowWidth !== 0
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { act } from 'react'
2+
import { useEffect } from 'react'
3+
import { JSDOM } from 'jsdom'
4+
import type { SelectorType } from '@/components/inputs/Selector'
5+
import { useHandleSelectorComponent } from '@/hooks/useHandleSelectorComponent'
6+
7+
const initialItem = { id: 'initial' }
8+
const updatedItem = { id: 'updated' }
9+
const templateSelector = 'templateSelected' as SelectorType
10+
11+
const setupDom = () => {
12+
const dom = new JSDOM('<!doctype html><html><body></body></html>', { url: 'http://localhost' })
13+
const previousWindow = globalThis.window
14+
const previousDocument = globalThis.document
15+
const previousNavigator = globalThis.navigator
16+
const previousActEnvironment = Object.getOwnPropertyDescriptor(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
17+
18+
Object.defineProperty(globalThis, 'window', { configurable: true, value: dom.window })
19+
Object.defineProperty(globalThis, 'document', { configurable: true, value: dom.window.document })
20+
Object.defineProperty(globalThis, 'navigator', { configurable: true, value: dom.window.navigator })
21+
Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', { configurable: true, value: true })
22+
23+
return () => {
24+
Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow })
25+
Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument })
26+
Object.defineProperty(globalThis, 'navigator', { configurable: true, value: previousNavigator })
27+
if (previousActEnvironment) {
28+
Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', previousActEnvironment)
29+
} else {
30+
Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT')
31+
}
32+
dom.window.close()
33+
}
34+
}
35+
36+
describe('useHandleSelectorComponent', () => {
37+
it('keeps the imperative updater stable after local selector state changes', async () => {
38+
const cleanupDom = setupDom()
39+
const { createRoot } = await import('react-dom/client')
40+
const container = document.createElement('div')
41+
document.body.appendChild(container)
42+
const root = createRoot(container)
43+
const effectRuns = jest.fn()
44+
45+
const Probe = () => {
46+
const { updateRenderingItem } = useHandleSelectorComponent({
47+
item: initialItem,
48+
type: templateSelector,
49+
})
50+
51+
useEffect(() => {
52+
effectRuns()
53+
updateRenderingItem(updatedItem)
54+
}, [updateRenderingItem])
55+
56+
return null
57+
}
58+
59+
await act(async () => {
60+
root.render(<Probe />)
61+
})
62+
63+
expect(effectRuns).toHaveBeenCalledTimes(1)
64+
65+
act(() => {
66+
root.unmount()
67+
})
68+
container.remove()
69+
cleanupDom()
70+
})
71+
})

src/hooks/useHandleSelectorComponent.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { setCreateTemplateFields } from '@/redux/features/templateSlice'
44
import store from '@/redux/store'
55
import { WorkflowStateResponse } from '@/types/dto/workflowStates.dto'
66
import { HandleSelectorComponentModes } from '@/types/interfaces'
7-
import { useEffect, useState } from 'react'
7+
import { useCallback, useEffect, useState } from 'react'
88

99
export const useHandleSelectorComponent = ({
1010
item,
@@ -17,9 +17,9 @@ export const useHandleSelectorComponent = ({
1717
}) => {
1818
const [renderingItem, setRenderingItem] = useState<unknown>(item)
1919

20-
const updateRenderingItem = (newValue: unknown) => {
20+
const updateRenderingItem = useCallback((newValue: unknown) => {
2121
setRenderingItem(newValue)
22-
}
22+
}, [])
2323

2424
useEffect(() => {
2525
//item can be null and we don't want this block to run if item is null, thus we are doing the below check for item

0 commit comments

Comments
 (0)