Simplify route hooks with shared query logic
Refactor AddEventModal state management
This commit is contained in:
@@ -33,12 +33,8 @@ const AddEventModal: React.FC<AddEventModalProps> = ({ isOpen, onClose, editEven
|
||||
|
||||
const { addEvent, updateEvent } = useEventsStore();
|
||||
|
||||
// Track previous props to reset form fields when they change (avoids setState in useEffect)
|
||||
const [prevEditEvent, setPrevEditEvent] = useState<Event | undefined>(editEvent);
|
||||
const [prevIsOpen, setPrevIsOpen] = useState(isOpen);
|
||||
if (editEvent !== prevEditEvent || isOpen !== prevIsOpen) {
|
||||
setPrevEditEvent(editEvent);
|
||||
setPrevIsOpen(isOpen);
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
if (editEvent) {
|
||||
setTitle(editEvent.title);
|
||||
setDestination(editEvent.destination);
|
||||
@@ -51,7 +47,7 @@ const AddEventModal: React.FC<AddEventModalProps> = ({ isOpen, onClose, editEven
|
||||
setEventTime("");
|
||||
}
|
||||
setSuccess(false);
|
||||
}
|
||||
}, [editEvent, isOpen]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -1,56 +1,16 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import type { BikeRoute } from "@timetoleave/core";
|
||||
import { ApiClient } from "@timetoleave/api-client";
|
||||
import { api } from "@/lib/api";
|
||||
import { useRouteQuery } from "./useRouteQuery";
|
||||
|
||||
const client = new ApiClient();
|
||||
|
||||
/**
|
||||
* Bike route hook. Fetches a bicycle route from origin to destination
|
||||
* via OSRM. Skips the request if any coordinate is undefined.
|
||||
*/
|
||||
export function useBikeRoute(
|
||||
fromLat: number | undefined,
|
||||
fromLng: number | undefined,
|
||||
toLat: number | undefined,
|
||||
toLng: number | undefined,
|
||||
) {
|
||||
const [bikeRoute, setBikeRoute] = useState<BikeRoute | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const fetchRoute = async () => {
|
||||
if (fromLat == null || fromLng == null || toLat == null || toLng == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const data = await client.getBikeRoute(fromLat, fromLng, toLat, toLng);
|
||||
|
||||
if (isMounted) {
|
||||
setBikeRoute(data);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
if (isMounted) {
|
||||
const message = err instanceof Error ? err.message : "Failed to fetch bike route";
|
||||
setError(message);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchRoute();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [fromLat, fromLng, toLat, toLng]);
|
||||
|
||||
const { data: bikeRoute, loading, error } = useRouteQuery<BikeRoute>(
|
||||
(a, b, c, d) => api.getBikeRoute(a, b, c, d),
|
||||
fromLat, fromLng, toLat, toLng,
|
||||
);
|
||||
return { bikeRoute, loading, error };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
type RouteFetcher<T> = (fromLat: number, fromLng: number, toLat: number, toLng: number) => Promise<T>;
|
||||
|
||||
/**
|
||||
* Shared fetch logic for OSRM-backed route hooks (bike, walk).
|
||||
* Uses a ref for the fetcher so swapping it never triggers a re-fetch —
|
||||
* only coordinate changes do.
|
||||
*
|
||||
* @param clearOnMissing - When true, resets data/error to null when coords are absent.
|
||||
*/
|
||||
export function useRouteQuery<T>(
|
||||
fetcher: RouteFetcher<T>,
|
||||
fromLat: number | undefined,
|
||||
fromLng: number | undefined,
|
||||
toLat: number | undefined,
|
||||
toLng: number | undefined,
|
||||
clearOnMissing = false,
|
||||
) {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const fetcherRef = useRef(fetcher);
|
||||
fetcherRef.current = fetcher;
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
if (fromLat == null || fromLng == null || toLat == null || toLng == null) {
|
||||
if (clearOnMissing) {
|
||||
setData(null);
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetcherRef.current(fromLat, fromLng, toLat, toLng)
|
||||
.then((result) => {
|
||||
if (isMounted) {
|
||||
setData(result);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (isMounted) {
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch route");
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fromLat, fromLng, toLat, toLng, clearOnMissing]);
|
||||
|
||||
return { data, loading, error };
|
||||
}
|
||||
@@ -1,57 +1,17 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import type { WalkRoute } from "@timetoleave/core";
|
||||
import { api as client } from "@/lib/api";
|
||||
import { api } from "@/lib/api";
|
||||
import { useRouteQuery } from "./useRouteQuery";
|
||||
|
||||
/**
|
||||
* Walk route hook. Fetches a walking route between two coordinates
|
||||
* via OSRM (proxied through the server). Clears state when coordinates are null.
|
||||
*/
|
||||
export function useWalkRoute(
|
||||
fromLat: number | undefined,
|
||||
fromLng: number | undefined,
|
||||
toLat: number | undefined,
|
||||
toLng: number | undefined,
|
||||
) {
|
||||
const [walkRoute, setWalkRoute] = useState<WalkRoute | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const fetchRoute = async () => {
|
||||
if (fromLat == null || fromLng == null || toLat == null || toLng == null) {
|
||||
setWalkRoute(null);
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const data = await client.getWalkRoute(fromLat, fromLng, toLat, toLng);
|
||||
|
||||
if (isMounted) {
|
||||
setWalkRoute(data);
|
||||
setLoading(false);
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
if (isMounted) {
|
||||
const message = err instanceof Error ? err.message : "Failed to fetch walk route";
|
||||
setError(message);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fetchRoute();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, [fromLat, fromLng, toLat, toLng]);
|
||||
|
||||
const { data: walkRoute, loading, error } = useRouteQuery<WalkRoute>(
|
||||
(a, b, c, d) => api.getWalkRoute(a, b, c, d),
|
||||
fromLat, fromLng, toLat, toLng,
|
||||
true,
|
||||
);
|
||||
return { walkRoute, loading, error };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user