commit 7d4778491950333bab101f95fc3ce586a148e56b
Author: Vaclav Honzik <vaclavhonzik98@gmail.com>
Date:   Thu May 19 11:44:52 2022 +0200

    reidxing + drag jump fix
    
    re #9741

diff --git a/frontend/src/features/TrackingTool/DraggableList/DraggableList.tsx b/frontend/src/features/TrackingTool/DraggableList/DraggableList.tsx
index 267b962..8ae595c 100644
--- a/frontend/src/features/TrackingTool/DraggableList/DraggableList.tsx
+++ b/frontend/src/features/TrackingTool/DraggableList/DraggableList.tsx
@@ -1,5 +1,9 @@
 import { memo } from 'react'
-import { DragDropContext, Droppable, OnDragEndResponder } from 'react-beautiful-dnd'
+import {
+    DragDropContext,
+    Droppable,
+    OnDragEndResponder,
+} from 'react-beautiful-dnd'
 import { MapPoint } from '../Map/pathUtils'
 import DraggableListItem from './DraggableListItem'
 
@@ -9,6 +13,16 @@ export interface DraggableListProps {
 }
 
 const DraggableList = memo(({ items, onDragEnd }: DraggableListProps) => {
+    window.addEventListener('error', (e) => {
+        if (
+            e.message ===
+                'ResizeObserver loop completed with undelivered notifications.' ||
+            e.message === 'ResizeObserver loop limit exceeded'
+        ) {
+            e.stopImmediatePropagation()
+        }
+    })
+
     return (
         <DragDropContext onDragEnd={onDragEnd}>
             <Droppable droppableId="droppable-list">
@@ -17,7 +31,7 @@ const DraggableList = memo(({ items, onDragEnd }: DraggableListProps) => {
                         {items.map((item, index) => (
                             <DraggableListItem
                                 list={items}
-                                index={index}
+                                idx={index}
                                 key={item.id}
                             />
                         ))}
diff --git a/frontend/src/features/TrackingTool/DraggableList/DraggableListItem.tsx b/frontend/src/features/TrackingTool/DraggableList/DraggableListItem.tsx
index 05c4aae..1bf2d30 100644
--- a/frontend/src/features/TrackingTool/DraggableList/DraggableListItem.tsx
+++ b/frontend/src/features/TrackingTool/DraggableList/DraggableListItem.tsx
@@ -5,18 +5,17 @@ import {
     ListItemAvatar,
     ListItemText,
 } from '@mui/material'
-import makeStyles from '@mui/material/styles/makeStyles'
 import { Draggable } from 'react-beautiful-dnd'
 import { MapPoint, PathVariant } from '../Map/pathUtils'
 import LocationOnIcon from '@mui/icons-material/LocationOn'
 import { CatalogItemDto } from '../../../swagger/data-contracts'
 import { useDispatch } from 'react-redux'
 import { updateMapMarker, updateMapMarkerWithId } from '../trackingToolSlice'
-import { Fragment, useEffect, useState } from 'react'
+import { useMemo } from 'react'
 
 export type DraggableListItemProps = {
     list: PathVariant
-    index: number
+    idx: number
 }
 
 const getFormattedLocationOrEmpty = (catalogItem: CatalogItemDto) => {
@@ -27,50 +26,61 @@ const getFormattedLocationOrEmpty = (catalogItem: CatalogItemDto) => {
     return `${catalogItem.latitude}°, ${catalogItem.longitude}°`
 }
 
-const DraggableListItem = ({ list, index }: DraggableListItemProps) => {
-    const item = list[index]
+const DraggableListItem = ({ list, idx }: DraggableListItemProps) => {
+    const item = list[idx]
     const dispatch = useDispatch()
-    const toggleHidden = () => {
-        dispatch(
-            updateMapMarkerWithId({
-                item: {
-                    ...item,
-                    active: !item?.active,
-                } as MapPoint,
-                id: item.id,
-            })
-        )
-    }
 
-    return (
-        <Draggable key={`${item.id}`} draggableId={`${item.id}`} index={index}>
-            {(provided, snapshot) => (
-                <ListItem
-                    ref={provided.innerRef}
-                    {...provided.draggableProps}
-                    {...provided.dragHandleProps}
-                    sx={{
-                        background: snapshot.isDragging
-                            ? 'rgb(235,235,235)'
-                            : 'inherit',
-                    }}
-                >
-                    <ListItemAvatar>
-                        <Avatar>
-                            <LocationOnIcon />
-                        </Avatar>
-                    </ListItemAvatar>
-                    <ListItemText
-                        primary={item.catalogItem.name ?? 'Unknown name'}
-                        secondary={getFormattedLocationOrEmpty(
-                            item.catalogItem
-                        )}
-                    />
-                    <Checkbox checked={item.active} onChange={toggleHidden} />
-                </ListItem>
-            )}
-        </Draggable>
-    )
+    // useMemo to prevent unnecessary re-renders which will make the list jumpy
+    return useMemo(() => {
+        const toggleHidden = () => {
+            dispatch(
+                updateMapMarkerWithId({
+                    item: {
+                        ...item,
+                        active: !item?.active,
+                    } as MapPoint,
+                    id: item.id,
+                })
+            )
+        }
+
+        return (
+            <Draggable
+                key={`${item.id}`}
+                draggableId={`${item.id}`}
+                index={idx}
+            >
+                {(provided, snapshot) => (
+                    <ListItem
+                        ref={provided.innerRef}
+                        {...provided.draggableProps}
+                        {...provided.dragHandleProps}
+                        sx={{
+                            background: snapshot.isDragging
+                                ? 'rgb(235,235,235)'
+                                : 'inherit',
+                        }}
+                    >
+                        <ListItemAvatar>
+                            <Avatar>
+                                <LocationOnIcon />
+                            </Avatar>
+                        </ListItemAvatar>
+                        <ListItemText
+                            primary={item.catalogItem.name ?? 'Unknown name'}
+                            secondary={getFormattedLocationOrEmpty(
+                                item.catalogItem
+                            )}
+                        />
+                        <Checkbox
+                            checked={item.active}
+                            onChange={toggleHidden}
+                        />
+                    </ListItem>
+                )}
+            </Draggable>
+        )
+    }, [item, idx, dispatch])
 }
 
 export default DraggableListItem
diff --git a/frontend/src/features/TrackingTool/DraggableList/DraggableMarkerList.tsx b/frontend/src/features/TrackingTool/DraggableList/DraggableMarkerList.tsx
index 42dce2c..6bd7685 100644
--- a/frontend/src/features/TrackingTool/DraggableList/DraggableMarkerList.tsx
+++ b/frontend/src/features/TrackingTool/DraggableList/DraggableMarkerList.tsx
@@ -1,9 +1,9 @@
 import { Paper } from '@mui/material'
-import { useEffect, useState } from 'react'
+import { useEffect, useState, useCallback } from 'react'
 import { DropResult } from 'react-beautiful-dnd'
 import { useDispatch, useSelector } from 'react-redux'
 import { RootState } from '../../redux/store'
-import { swapMapMarkerIndices } from '../trackingToolSlice'
+import { moveMarkerToDestination } from '../trackingToolSlice'
 import { PathVariant } from '../Map/pathUtils'
 import DraggableList from './DraggableList'
 
@@ -33,18 +33,18 @@ const DraggableMarkerList = () => {
         setPath(paths[primaryPathIdx])
     }, [paths, primaryPathIdx])
 
-    const onDragEnd = ({ destination, source }: DropResult) => {
+    const onDragEnd = useCallback(({ destination, source }: DropResult) => {
         if (!destination || !source || destination.index === source.index) {
             return
         }
 
         dispatch(
-            swapMapMarkerIndices({
+            moveMarkerToDestination({
                 source: source.index,
                 destination: destination.index,
             })
         )
-    }
+    }, [dispatch])
 
     return (
         <Paper variant="outlined">
diff --git a/frontend/src/features/TrackingTool/Map/MapPath.tsx b/frontend/src/features/TrackingTool/Map/MapPath.tsx
index 69801b0..2f56ba8 100644
--- a/frontend/src/features/TrackingTool/Map/MapPath.tsx
+++ b/frontend/src/features/TrackingTool/Map/MapPath.tsx
@@ -3,7 +3,11 @@ import { useDispatch, useSelector } from 'react-redux'
 import { RootState } from '../../redux/store'
 import { PathVariant, MapPoint, isMapPointDisplayable } from './pathUtils'
 import TextPath from 'react-leaflet-textpath'
-import { setPrimaryIdx, updateMapMarker, updateMapMarkerWithId } from '../trackingToolSlice'
+import {
+    setPrimaryIdx,
+    updateMapMarker,
+    updateMapMarkerWithId,
+} from '../trackingToolSlice'
 import MapMarker from './MapMarker'
 import { LatLngTuple } from 'leaflet'
 import { Popup, Tooltip } from 'react-leaflet'
@@ -75,7 +79,9 @@ const MapPath: FunctionComponent<MapPathProps> = ({ idx }) => {
             edges.push(
                 <TextPath
                     // Somehow this refuses to work so let it rerender everything ...
-                    key={`${activeMapPoints[i].id}-${activeMapPoints[i + 1].id}`}
+                    key={`${activeMapPoints[i].id}-${
+                        activeMapPoints[i + 1].id
+                    }`}
                     positions={[
                         [start.latitude, start.longitude],
                         [end.latitude, end.longitude],
@@ -153,10 +159,8 @@ const MapPath: FunctionComponent<MapPathProps> = ({ idx }) => {
                                                 onChange={() => {
                                                     dispatch(
                                                         updateMapMarker({
-                                                            item: {
-                                                                ...item,
-                                                                active: !item.active,
-                                                            },
+                                                            ...item,
+                                                            active: !item.active,
                                                         })
                                                     )
                                                 }}
diff --git a/frontend/src/features/TrackingTool/trackingToolSlice.ts b/frontend/src/features/TrackingTool/trackingToolSlice.ts
index b87f854..620ea88 100644
--- a/frontend/src/features/TrackingTool/trackingToolSlice.ts
+++ b/frontend/src/features/TrackingTool/trackingToolSlice.ts
@@ -81,8 +81,8 @@ export const trackingToolSlice = createSlice({
             }
         },
         // Updates map marker based on its idx property
-        updateMapMarker: (state: TrackingToolState, action: { payload: { item: MapPoint } }) => {
-            const { item } = action.payload
+        updateMapMarker: (state: TrackingToolState, action: { payload: MapPoint }) => {
+            const item = action.payload
             const idx = state.primaryPathIdx
             if (!state.pathVariants || state.pathVariants.length <= idx) {
                 return state
@@ -103,7 +103,7 @@ export const trackingToolSlice = createSlice({
                 })
             }
         },
-        swapMapMarkerIndices: (state: TrackingToolState, action: { payload: { destination: number, source: number } }) => {
+        moveMarkerToDestination: (state: TrackingToolState, action: { payload: { destination: number, source: number } }) => {
             const { destination, source } = action.payload
             if (!state.pathVariants || state.pathVariants.length === 0) {
                 return state
@@ -124,7 +124,7 @@ export const trackingToolSlice = createSlice({
                     const result = [...pathVariant]
                     const [removed] = result.splice(source, 1)
                     result.splice(destination, 0, removed)
-                    return result
+                    return result.map((item, idx) => ({ ...item, idx }))
                 })
             }
         },
@@ -230,7 +230,7 @@ export const {
     clear,
     updateMapMarker,
     mergeWithCurrentPath,
-    swapMapMarkerIndices,
+    moveMarkerToDestination,
     updateMapMarkerWithId
 } = trackingToolSlice.actions
 const trackingToolReducer = trackingToolSlice.reducer
