refactored to use reducer instead of explicit state change in component

main
Inga 🏳‍🌈 6 months ago
parent 6dab7c18cc
commit 748334108c
  1. 119
      src/routePlanner/index.tsx
  2. 4
      src/routePlanner/marker.tsx
  3. 79
      src/routePlanner/reducer.ts
  4. 20
      src/routePlanner/types.ts

@ -1,96 +1,71 @@
import { nanoid } from 'nanoid';
import { useCallback, useState } from 'preact/hooks';
import { reorderElements } from '../shared/collections';
import { useCallback, useReducer } from 'preact/hooks';
import { Coordinates } from '../shared/types';
import { ExportComponent } from './export';
import { MapComponent } from './map';
import { MarkersComponent } from './markers';
import { Marker, ReorderMarkersParams } from './types';
import { ReorderMarkersParams } from './types';
import './style.css';
import { markersReducer } from './reducer';
export const RoutePlanner = () => {
const [markers, setMarkers] = useState<Marker[]>([]);
const [markers, dispatchMarkers] = useReducer(markersReducer, []);
const onMarkersReorder = useCallback(
const reorderMarkers = useCallback(
({ oldIndex, newIndex }: ReorderMarkersParams): void =>
setMarkers((oldMarkers) => {
console.log(
`Reordering markers: from ${oldIndex} to ${newIndex}`,
);
const newMarkers = reorderElements(
oldMarkers,
oldIndex,
newIndex,
);
return newMarkers.map((marker, i) => ({
...marker,
label: `${i + 1}`,
remove: () =>
onMarkersReorder({
oldIndex: i,
newIndex: -1,
}),
...(i - 1 >= 0 && {
moveDown: () =>
onMarkersReorder({
oldIndex: i,
newIndex: i - 1,
}),
}),
...(i + 1 < newMarkers.length && {
moveUp: () =>
onMarkersReorder({
oldIndex: i,
newIndex: i + 1,
}),
}),
}));
dispatchMarkers({
type: 'reorder',
data: { oldIndex, newIndex },
}),
[],
);
const onMapClick = useCallback(
(coordinates: Coordinates) => {
setMarkers((markers) => [
...markers.slice(0, markers.length - 1),
...markers
.slice(markers.length - 1, markers.length)
.map((marker) => ({
...marker,
moveUp: () =>
onMarkersReorder({
oldIndex: markers.length - 1,
newIndex: markers.length,
}),
})),
{
key: nanoid(10),
label: `${markers.length + 1}`,
remove: () =>
onMarkersReorder({
oldIndex: markers.length,
newIndex: -1,
}),
...(markers.length && {
moveDown: () =>
onMarkersReorder({
oldIndex: markers.length,
newIndex: markers.length - 1,
}),
}),
const moveMarkerUp = useCallback(
(key: string) =>
dispatchMarkers({
type: 'moveUp',
data: { key },
}),
[],
);
const moveMarkerDown = useCallback(
(key: string) =>
dispatchMarkers({
type: 'moveDown',
data: { key },
}),
[],
);
const removeMarker = useCallback(
(key: string) =>
dispatchMarkers({
type: 'remove',
data: { key },
}),
[],
);
const addMarker = useCallback(
(coordinates: Coordinates) =>
dispatchMarkers({
type: 'add',
data: {
coordinates,
moveUp: moveMarkerUp,
moveDown: moveMarkerDown,
remove: removeMarker,
},
]);
},
[onMarkersReorder],
}),
[moveMarkerUp, moveMarkerDown, removeMarker],
);
return (
<section class="route-planner">
<section class="markers">
<MarkersComponent
onMarkersReorder={onMarkersReorder}
onMarkersReorder={reorderMarkers}
markers={markers}
/>
</section>
@ -98,7 +73,7 @@ export const RoutePlanner = () => {
<ExportComponent markers={markers} />
</section>
<section class="map">
<MapComponent onMapClick={onMapClick} markers={markers} />
<MapComponent onMapClick={addMarker} markers={markers} />
</section>
</section>
);

@ -5,10 +5,10 @@ export const MarkerComponent = ({ marker }: MarkerProps) => {
<li key={marker.key}>
{`Waypoint ${marker.label} (${marker.key.substring(0, 4)})`}
<span class="buttons">
<button onClick={marker.moveDown} disabled={!marker.moveDown}>
<button onClick={marker.moveUp} disabled={!marker.moveUp}>
🔼
</button>
<button onClick={marker.moveUp} disabled={!marker.moveUp}>
<button onClick={marker.moveDown} disabled={!marker.moveDown}>
🔽
</button>
<button onClick={marker.remove}>🗑</button>

@ -0,0 +1,79 @@
import { nanoid } from 'nanoid';
import { Marker, ReducerAction } from './types';
import { reorderElements } from '../shared/collections';
const reindexMarkers = (markers: Marker[]) =>
markers.map((marker, i) => {
const newLabel = `${i + 1}`;
if (marker.label === newLabel) {
return marker;
}
return {
...marker,
label: newLabel,
};
});
export const markersReducer = (
markers: Marker[],
action: ReducerAction,
): Marker[] => {
const type = action.type;
switch (action.type) {
case 'add': {
const key = nanoid(10);
return reindexMarkers([
...markers,
{
key,
label: 'placeholder',
remove: () => action.data.remove(key),
moveUp: () => action.data.moveUp(key),
moveDown: () => action.data.moveDown(key),
coordinates: action.data.coordinates,
},
]);
}
case 'remove': {
return reindexMarkers([
...markers.filter(({ key }) => key !== action.data.key),
]);
}
case 'moveUp': {
const oldIndex = markers.findIndex(
({ key }) => key === action.data.key,
);
if (oldIndex < 1) {
return markers;
}
return reindexMarkers(
reorderElements(markers, oldIndex, oldIndex - 1),
);
}
case 'moveDown': {
const oldIndex = markers.findIndex(
({ key }) => key === action.data.key,
);
if (oldIndex < 0 || oldIndex + 1 >= markers.length) {
return markers;
}
return reindexMarkers(
reorderElements(markers, oldIndex, oldIndex + 1),
);
}
case 'reorder': {
return reindexMarkers(
reorderElements(
markers,
action.data.oldIndex,
action.data.newIndex,
),
);
}
default:
throw new Error(`Unsupported type ${type}`);
}
};

@ -8,6 +8,26 @@ export type Marker = Waypoint & {
label: string;
};
type GenericReducerAction<TType extends string, TData> = {
type: TType;
data: TData;
};
export type ReducerAction =
| GenericReducerAction<
'add',
{
coordinates: Coordinates;
remove: (key: string) => void;
moveUp: (key: string) => void;
moveDown: (key: string) => void;
}
>
| GenericReducerAction<'remove', { key: string }>
| GenericReducerAction<'moveUp', { key: string }>
| GenericReducerAction<'moveDown', { key: string }>
| GenericReducerAction<'reorder', { oldIndex: number; newIndex: number }>;
export type ReorderMarkersParams = {
oldIndex: number;
newIndex: number;

Loading…
Cancel
Save