diff --git a/src/app/configure-tasks-app/ui/TemplateSidebar.tsx b/src/app/configure-tasks-app/ui/TemplateSidebar.tsx index 9d9339b71..75b5d3afe 100644 --- a/src/app/configure-tasks-app/ui/TemplateSidebar.tsx +++ b/src/app/configure-tasks-app/ui/TemplateSidebar.tsx @@ -48,7 +48,7 @@ export const TemplateSidebar = ({ const currentWorkflowState = workflowStates.find((el) => el?.id === currentTask?.workflowStateId) updateStatusValue(currentWorkflowState) } - }, [activeTemplate, workflowStates]) + }, [activeTemplate, workflowStates, updateStatusValue]) const windowWidth = useWindowWidth() const isMobile = windowWidth < 800 && windowWidth !== 0 diff --git a/src/hooks/useHandleSelectorComponent.test.tsx b/src/hooks/useHandleSelectorComponent.test.tsx new file mode 100644 index 000000000..8e4ab197b --- /dev/null +++ b/src/hooks/useHandleSelectorComponent.test.tsx @@ -0,0 +1,71 @@ +import { act } from 'react' +import { useEffect } from 'react' +import { JSDOM } from 'jsdom' +import type { SelectorType } from '@/components/inputs/Selector' +import { useHandleSelectorComponent } from '@/hooks/useHandleSelectorComponent' + +const initialItem = { id: 'initial' } +const updatedItem = { id: 'updated' } +const templateSelector = 'templateSelected' as SelectorType + +const setupDom = () => { + const dom = new JSDOM('', { url: 'http://localhost' }) + const previousWindow = globalThis.window + const previousDocument = globalThis.document + const previousNavigator = globalThis.navigator + const previousActEnvironment = Object.getOwnPropertyDescriptor(globalThis, 'IS_REACT_ACT_ENVIRONMENT') + + Object.defineProperty(globalThis, 'window', { configurable: true, value: dom.window }) + Object.defineProperty(globalThis, 'document', { configurable: true, value: dom.window.document }) + Object.defineProperty(globalThis, 'navigator', { configurable: true, value: dom.window.navigator }) + Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', { configurable: true, value: true }) + + return () => { + Object.defineProperty(globalThis, 'window', { configurable: true, value: previousWindow }) + Object.defineProperty(globalThis, 'document', { configurable: true, value: previousDocument }) + Object.defineProperty(globalThis, 'navigator', { configurable: true, value: previousNavigator }) + if (previousActEnvironment) { + Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', previousActEnvironment) + } else { + Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT') + } + dom.window.close() + } +} + +describe('useHandleSelectorComponent', () => { + it('keeps the imperative updater stable after local selector state changes', async () => { + const cleanupDom = setupDom() + const { createRoot } = await import('react-dom/client') + const container = document.createElement('div') + document.body.appendChild(container) + const root = createRoot(container) + const effectRuns = jest.fn() + + const Probe = () => { + const { updateRenderingItem } = useHandleSelectorComponent({ + item: initialItem, + type: templateSelector, + }) + + useEffect(() => { + effectRuns() + updateRenderingItem(updatedItem) + }, [updateRenderingItem]) + + return null + } + + await act(async () => { + root.render() + }) + + expect(effectRuns).toHaveBeenCalledTimes(1) + + act(() => { + root.unmount() + }) + container.remove() + cleanupDom() + }) +}) diff --git a/src/hooks/useHandleSelectorComponent.tsx b/src/hooks/useHandleSelectorComponent.tsx index 99ba92a8e..6070a61b7 100644 --- a/src/hooks/useHandleSelectorComponent.tsx +++ b/src/hooks/useHandleSelectorComponent.tsx @@ -4,7 +4,7 @@ import { setCreateTemplateFields } from '@/redux/features/templateSlice' import store from '@/redux/store' import { WorkflowStateResponse } from '@/types/dto/workflowStates.dto' import { HandleSelectorComponentModes } from '@/types/interfaces' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' export const useHandleSelectorComponent = ({ item, @@ -17,9 +17,9 @@ export const useHandleSelectorComponent = ({ }) => { const [renderingItem, setRenderingItem] = useState(item) - const updateRenderingItem = (newValue: unknown) => { + const updateRenderingItem = useCallback((newValue: unknown) => { setRenderingItem(newValue) - } + }, []) useEffect(() => { //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