Remove useEffects and fix label selection regression#5781
Remove useEffects and fix label selection regression#5781jpggvilaca wants to merge 16 commits intodevelopfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce unnecessary React renders by removing “sync state from props” useEffects, while also silencing the React Router v7 startTransition warning by enabling the future.v7_startTransition flag on RouterProvider.
Changes:
- Enable
future.v7_startTransitiononRouterProviderin both runtime providers and test render utilities. - Refactor several components/hooks to avoid prop→state syncing via
useEffectby using draft state, memoized derived values, or remount keys. - Adjust annotator provider composition and polygon editing mounting behavior.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| application/ui/src/test-utils/render.tsx | Passes React Router future flag in test RouterProvider to remove warnings. |
| application/ui/src/providers.tsx | Passes React Router future flag in app RouterProvider. |
| application/ui/src/shared/annotator/select-annotation-provider.component.tsx | Removes resetKey prop and effect-based reset for selected annotations. |
| application/ui/src/features/dataset/media-preview/annotator-providers.component.tsx | Reorders providers; nests SelectAnnotationProvider inside AnnotationActionsProvider. |
| application/ui/src/features/models/train-model/advanced-settings/components/range-parameter-field/range-parameter-field.component.tsx | Replaces effect-driven syncing with draft range state. |
| application/ui/src/features/models/train-model/advanced-settings/components/number-parameter-field.component.tsx | Replaces effect-driven syncing with draft value state for slider interactions. |
| application/ui/src/features/annotator/video-player/video-toolbar/video-annotator/video-timeline/video-player-slider/video-player-slider.component.tsx | Uses a drag-only draft frame value instead of syncing local state via effect. |
| application/ui/src/features/annotator/tools/ssim-tool/use-ssim.hook.ts | Moves preview reset logic from an effect into updateToolState. |
| application/ui/src/features/annotator/tools/hooks/use-polygon-config.hook.tsx | Replaces polygon state + effects with memoized derived polygon. |
| application/ui/src/features/annotator/tools/edit-polygon/edit-polygon.component.tsx | Removes effect that synced local shape state to prop changes. |
| application/ui/src/features/annotator/annotations/editable-annotation.component.tsx | Forces polygon editor remount via a key derived from polygon points. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
application/ui/src/features/dataset/media-preview/annotator-providers.component.tsx
Show resolved
Hide resolved
application/ui/src/features/annotator/annotations/editable-annotation.component.tsx
Outdated
Show resolved
Hide resolved
📊 Test coverage report
|
Docker Image SizesCPU
CUDA
XPU
|
application/ui/src/features/annotator/annotations/editable-annotation.component.tsx
Outdated
Show resolved
Hide resolved
application/ui/src/features/annotator/labels/labels.component.test.tsx
Outdated
Show resolved
Hide resolved
application/ui/src/features/dataset/media-preview/annotator-providers.component.tsx
Outdated
Show resolved
Hide resolved
...model/advanced-settings/components/range-parameter-field/range-parameter-field.component.tsx
Outdated
Show resolved
Hide resolved
| step, | ||
| }: NumberGroupParamsProps) => { | ||
| const [parameterValue, setParameterValue] = useState<number>(value); | ||
| const [draftValue, setDraftValue] = useState<number | null>(null); |
There was a problem hiding this comment.
same here, value is never null
5703300 to
31f2b9c
Compare
| }} | ||
| onChangeEnd={(newFrameNumber) => { | ||
| selectFrame(newFrameNumber); | ||
| setDragFrameNumber(null); |
There was a problem hiding this comment.
why value is updated to null on end?
There was a problem hiding this comment.
Or else line 39 const parameterValue = draftRange ?? { start: value[0], end: value[1] }; will always have a draftRange, when we wouldnt be able to reset.
| }: VideoPlayerSliderProps) => { | ||
| const [sliderValue, setSliderValue] = useState<number>(frameNumber); | ||
|
|
||
| useEffect(() => setSliderValue(frameNumber), [frameNumber]); |
There was a problem hiding this comment.
Have u checked different scenarios like playing video, selecting next/prev frame etc?
| } | ||
|
|
||
| onChange([parameterValue.start, parameterValue.end]); | ||
| setDraftRange(null); |
There was a problem hiding this comment.
same here, why setting that to null?
There was a problem hiding this comment.
Answered above. But there was a bug though. handleRangeChangeEnd should use the input Value and not the draft. thanks!
| await test.step('Select annotation on media 1', async () => { | ||
| await page.getByRole('button', { name: 'selection tool' }).click(); | ||
| await page.getByLabel('annotation rect').nth(1).click(); | ||
| }); | ||
|
|
||
| await test.step('Switch to media 2 and back to media 1', async () => { | ||
| const sidebarItems = page.getByRole('listbox', { name: 'sidebar-items' }); | ||
| await sidebarItems.getByRole('img', { name: 'item-2.jpg' }).click(); | ||
| await expect(annotatorPage.getAnnotationsList()).toBeVisible(); | ||
|
|
||
| await sidebarItems.getByRole('img', { name: 'item-1.jpg' }).click(); | ||
| await expect(annotatorPage.getAnnotationsList()).toBeVisible(); | ||
| expect(await annotatorPage.getAnnotationsListItems('annotation rect')).toHaveLength(1); | ||
| }); |
There was a problem hiding this comment.
this test only checks if annotation is visible, it does not check if annotation is not selected anymore.
There was a problem hiding this comment.
Indeed, added aria label to selected annotation and updated test
| }; | ||
|
|
||
| export const SelectAnnotationProvider = ({ children, resetKey }: { children: ReactNode; resetKey?: string }) => { | ||
| export const SelectAnnotationProvider = ({ children }: { children: ReactNode }) => { |
There was a problem hiding this comment.
When do we reset selected annotations?
There was a problem hiding this comment.
We do not at the moment
…o reset the annotations, not only selected ones
680e476 to
bdbd1c1
Compare
application/ui/src/features/annotator/tools/hooks/use-polygon-config.hook.tsx
Show resolved
Hide resolved
| <VideoPlayerProvider | ||
| videoFrame={isVideoFrame(mediaItem) ? mediaItem : undefined} | ||
| changeSelectedMediaItem={selectMediaItem} | ||
| changeSelectedMediaItem={onSelectedMediaItem} |
There was a problem hiding this comment.
why this change? We need to update selected media in SelectedMediaProvider and update URL.
There was a problem hiding this comment.
onSelectedMediaItem will call SelectedMediaProvider's setItem. And updating the url is done by useSelectDatasetItem.
| type UseAnnotatorMediaTransitionProps = { | ||
| onSelectedMediaItem: (item: Media) => void; | ||
| }; | ||
| export const useAnnotatorMediaTransition = ({ onSelectedMediaItem }: UseAnnotatorMediaTransitionProps) => { |
There was a problem hiding this comment.
This code made me realize we have a useSelectDatasetItem hook that stores the media item in the URL and returns the selected item, while we also have SelectedMediaItemProvider / useSelectedMediaItem, which updates an internal setMediaItem state. Is that second approach really necessary?
There was a problem hiding this comment.
This is a valid point. We have useSelectDatasetItem taking care of routing. useSelectedMediaItem taking care of current media info, and now useAnnotatorMediaTransition to orchestrate media states. So the final answer is that we still need all
Summary
Tested manually but feel free to double check
How to test
Checklist