Skip to content

Commit 53d92ca

Browse files
authored
Merge pull request #27 from rezahedi/use-reducer
Use reducer to update each properties of the state
2 parents 03a1b1b + 2c4037f commit 53d92ca

File tree

5 files changed

+114
-69
lines changed

5 files changed

+114
-69
lines changed

src/context/ItineraryContext.tsx

Lines changed: 17 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { PlanType } from "./PlanTypes";
44
import { useAuth } from "./AuthContext";
55
import { useParams } from "react-router-dom";
66
import usePlanApi from "@/hooks/usePlanApi";
7+
import usePlanOptimistic from "@/hooks/usePlanOptimistic";
78

89
type contextType = {
910
plan: PlanType | null;
@@ -23,7 +24,8 @@ const ItineraryContext = createContext<contextType | undefined>(undefined);
2324
const ItineraryProvider = ({ children }: { children: React.ReactNode }) => {
2425
const { plan, setPlan, saving, loading, createPlan, getPlan, updatePlan } =
2526
usePlanApi();
26-
const [optimisticPlan, setOptimisticPlan] = useState<PlanType | null>(null);
27+
// const [optimisticPlan, setOptimisticPlan] = useState<PlanType | null>(null);
28+
const { optimisticPlan, dispatch } = usePlanOptimistic(null);
2729
const [error, setError] = useState<string | null>(null);
2830
const { token } = useAuth();
2931
const { planId } = useParams();
@@ -58,80 +60,36 @@ const ItineraryProvider = ({ children }: { children: React.ReactNode }) => {
5860
})();
5961
}, [optimisticPlan]);
6062

61-
// TODO: Use useReducer for setTitle, setDescription and etc, to make optimisticPlan update more cleanly
62-
6363
const setTitle = (title: string) => {
64-
if (!plan && !title) return;
65-
setOptimisticPlan((prev) => (prev ? { ...prev, title } : { title }));
64+
if (!plan || !title) return;
65+
66+
dispatch({ type: "setTitle", payload: title });
6667
};
6768

6869
const setDescription = (description: string) => {
69-
if (!plan && !description) return;
70+
if (!plan || !description) return;
7071

71-
setOptimisticPlan((prev) =>
72-
prev ? { ...prev, description } : { description },
73-
);
72+
dispatch({ type: "setDescription", payload: description });
7473
};
7574

7675
const addImage = (image: string) => {
77-
if (!plan) return;
78-
79-
setOptimisticPlan((prev) => {
80-
if (!prev) return null;
81-
if (!prev.images)
82-
return {
83-
...prev,
84-
images: [image],
85-
};
86-
return {
87-
...prev,
88-
images: [...prev.images, image],
89-
};
90-
});
91-
};
76+
if (!plan || !image) return;
9277

93-
const addPlace = (place: Place) => {
94-
if (!plan) return;
95-
96-
setOptimisticPlan((prev) => {
97-
// Initialize stops array using plan.stops if not existing in optimisticPlan
98-
if (!prev || !prev.stops) {
99-
prev = { ...prev, stops: plan.stops || [] };
100-
}
78+
setPlan({ ...plan, images: [...(plan.images || []), image] });
10179

102-
if (!prev.stops) {
103-
prev.stops = [place];
104-
return prev;
105-
}
80+
// dispatch({ type: "addImage", payload: image, init: plan.images || [] });
81+
};
10682

107-
// Avoid adding duplicates
108-
if (prev.stops.find((p) => p.placeId === place.placeId)) {
109-
return prev;
110-
}
83+
const addPlace = (place: Place) => {
84+
if (!plan || !place) return;
11185

112-
// Add new stop to the end
113-
return {
114-
...prev,
115-
stops: [...prev.stops, place],
116-
};
117-
});
86+
dispatch({ type: "addPlace", payload: place, init: plan.stops || [] });
11887
};
11988

12089
const removePlace = (placeId: string) => {
121-
if (!plan) return;
122-
123-
setOptimisticPlan((prev) => {
124-
// Initialize stops array using plan.stops if not existing in optimisticPlan
125-
if (!prev || !prev.stops) {
126-
prev = { ...prev, stops: plan.stops || [] };
127-
}
90+
if (!plan || !placeId) return;
12891

129-
if (!prev.stops) return prev;
130-
return {
131-
...prev,
132-
stops: prev.stops.filter((p) => p.placeId !== placeId),
133-
};
134-
});
92+
dispatch({ type: "removePlace", payload: placeId, init: plan.stops || [] });
13593
};
13694

13795
return (

src/hooks/usePlanOptimistic.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { PlaceType, PlanType } from "@/context/PlanTypes";
2+
import { useReducer } from "react";
3+
4+
type Action =
5+
| {
6+
type: "setTitle" | "setDescription";
7+
payload: string;
8+
}
9+
| {
10+
type: "addImage";
11+
payload: string;
12+
init: string[];
13+
}
14+
| {
15+
type: "removePlace";
16+
payload: string;
17+
init: PlaceType[];
18+
}
19+
| {
20+
type: "addPlace";
21+
payload: PlaceType;
22+
init: PlaceType[];
23+
};
24+
25+
const reducer = (state: PlanType | null, action: Action): PlanType | null => {
26+
switch (action.type) {
27+
case "setTitle":
28+
return {
29+
...state,
30+
title: action.payload,
31+
};
32+
33+
case "setDescription":
34+
return {
35+
...state,
36+
description: action.payload,
37+
};
38+
39+
case "addImage": {
40+
let images = [...(state?.images || action.init)];
41+
42+
images.push(action.payload);
43+
44+
return {
45+
...state,
46+
images,
47+
};
48+
}
49+
50+
case "addPlace": {
51+
let stops = [...(state?.stops || action.init)];
52+
53+
if (!stops.find((p) => p.placeId === action.payload.placeId))
54+
stops.push(action.payload);
55+
56+
return { ...state, stops };
57+
}
58+
59+
case "removePlace": {
60+
let stops = [...(state?.stops || action.init)];
61+
62+
return {
63+
...state,
64+
stops: stops.filter((p) => p.placeId !== action.payload),
65+
};
66+
}
67+
68+
default:
69+
return state;
70+
}
71+
};
72+
73+
export default function usePlanOptimistic(init: PlanType | null) {
74+
const [optimisticPlan, dispatch] = useReducer(reducer, init);
75+
76+
return { optimisticPlan, dispatch };
77+
}

src/pages/ExplorePage/places/PlacesMarkers.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,19 @@ import { Marker } from "@/Components/Map";
33
import { getMarkerIcon } from "@/types/map";
44
import { usePlans } from "../PlansContext";
55
import ListEditor from "./list/ListEditor";
6+
import { useMediaQuery } from "@/hooks/useMediaQuery";
67

78
const PlacesMarkers = function Markers() {
89
const { places, selection, setSelection } = usePlans();
910
const mouseOverTimeoutRef = useRef<number | null>(null);
11+
const { isMobile } = useMediaQuery();
12+
13+
const handleClick = useCallback(
14+
(placeId?: string, location?: [number, number]) => {
15+
if (placeId) setSelection({ placeId, location, source: "marker" });
16+
},
17+
[setSelection],
18+
);
1019

1120
const handleMouseOver = useCallback(
1221
(placeId?: string, location?: [number, number]) => {
@@ -48,8 +57,9 @@ const PlacesMarkers = function Markers() {
4857
// eslint-disable-next-line no-undef
4958
anchor: new google.maps.Point(19, 38),
5059
}}
51-
onMouseOver={handleMouseOver}
52-
onMouseOut={handleMouseOut}
60+
{...(isMobile
61+
? { onClick: handleClick }
62+
: { onMouseOver: handleMouseOver, onMouseOut: handleMouseOut })}
5363
/>
5464
))}
5565
<ListEditor />

src/pages/ExplorePage/places/PopupSkeleton.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { Skeleton } from "@/Components/ui/skeleton";
44
const PopupSkeleton = () => {
55
return (
66
<div className="flex gap-1 sm:w-xs sm:h-32">
7-
<Skeleton className="w-24 h-full rounded-sm hidden sm:block" />
7+
<Skeleton className="w-24 h-full rounded-sm hidden sm:block dark:bg-background/10" />
88
<div className="flex-4/5 max-h-40 px-2">
9-
<Skeleton className="w-9/12 h-7 mb-4" />
10-
<Skeleton className="h-4 mb-2" />
11-
<Skeleton className="h-4 w-8/12 mb-2" />
9+
<Skeleton className="w-9/12 h-7 mb-4 dark:bg-background/10" />
10+
<Skeleton className="h-4 mb-2 dark:bg-background/10" />
11+
<Skeleton className="h-4 w-8/12 mb-2 dark:bg-background/10" />
1212
</div>
1313
</div>
1414
);

src/pages/dashboard/create/PlacePopupSkeleton.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { Skeleton } from "@/Components/ui/skeleton";
44
const PlacePopupSkeleton = () => {
55
return (
66
<div className="flex gap-1 w-[230px] h-[128px]">
7-
<Skeleton className="w-24 h-full rounded-sm hidden sm:block" />
7+
<Skeleton className="w-24 h-full rounded-sm hidden sm:block dark:bg-background/10" />
88
<div className="flex-4/5 max-h-40 px-2">
9-
<Skeleton className="w-9/12 h-7 mb-4" />
10-
<Skeleton className="h-4 mb-2" />
11-
<Skeleton className="h-4 w-8/12 mb-2" />
9+
<Skeleton className="w-9/12 h-7 mb-4 dark:bg-background/10" />
10+
<Skeleton className="h-4 mb-2 dark:bg-background/10" />
11+
<Skeleton className="h-4 w-8/12 mb-2 dark:bg-background/10" />
1212
</div>
1313
</div>
1414
);

0 commit comments

Comments
 (0)