From 311a29cf4b274cb592392220d57f983bf1853447 Mon Sep 17 00:00:00 2001 From: Emmy D'Anello Date: Fri, 2 Feb 2024 00:50:50 +0100 Subject: [PATCH] Use local cache to optimize display --- sncf-station/package-lock.json | 68 +++++++++++++++ sncf-station/package.json | 3 + sncf-station/src/App.js | 26 +++++- sncf-station/src/Station.js | 25 +++--- sncf-station/src/TrainsTable.js | 142 ++++++++++++++++---------------- 5 files changed, 179 insertions(+), 85 deletions(-) diff --git a/sncf-station/package-lock.json b/sncf-station/package-lock.json index fa311ce..cae68ec 100644 --- a/sncf-station/package-lock.json +++ b/sncf-station/package-lock.json @@ -12,6 +12,9 @@ "@emotion/styled": "^11.11.0", "@mui/material": "^5.15.6", "@mui/x-date-pickers": "^6.19.2", + "@tanstack/query-sync-storage-persister": "^5.18.0", + "@tanstack/react-query": "^5.18.0", + "@tanstack/react-query-persist-client": "^5.18.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -4181,6 +4184,71 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@tanstack/query-core": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.18.0.tgz", + "integrity": "sha512-8c6nxeAnGHxIDZIyDmHdmgFt4D+LprAQaJmjsnM4szcIjsWOyFlzxdqQUuQ/XuQRvUgqYaqlJTtDADlSS7pTPQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-persist-client-core": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-persist-client-core/-/query-persist-client-core-5.18.0.tgz", + "integrity": "sha512-CXmUP8GYW1LL9tQC8vmuaK3XKWR3GaP8Bhy2bpgbIX5aOTN0qWL4zVema4hclgNahpTlY13P67Biab8ieB6frA==", + "dependencies": { + "@tanstack/query-core": "5.18.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-sync-storage-persister": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-sync-storage-persister/-/query-sync-storage-persister-5.18.0.tgz", + "integrity": "sha512-a+ztQhpnWRmQN8kn/IVscgxmSnBZER2TbrLkocZGzowecxT+Lm0RzbX+Dl2lVz92XXHFmhAjAXhWG61rbqQTng==", + "dependencies": { + "@tanstack/query-core": "5.18.0", + "@tanstack/query-persist-client-core": "5.18.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.18.0.tgz", + "integrity": "sha512-7FKxNfxxKEL7n3ADpwp81Fy4FX85hNkYVzQQVQsF0JAPl93c3d1gmNZMIbEtOqgYfom1/ontGh3FiZGYj3xyWA==", + "dependencies": { + "@tanstack/query-core": "5.18.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-query-persist-client": { + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-persist-client/-/react-query-persist-client-5.18.0.tgz", + "integrity": "sha512-XS3C3tMcnBosptRhn4kN5RBs6j8iRiE7tVr7XN73We4o3VNo86+zEe4iOwO+ziTq0Dawnc4kCjUVS3tZ2DiKhg==", + "dependencies": { + "@tanstack/query-persist-client-core": "5.18.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.18.0", + "react": "^18.0.0" + } + }, "node_modules/@testing-library/dom": { "version": "9.3.4", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.4.tgz", diff --git a/sncf-station/package.json b/sncf-station/package.json index 4161155..ce6c5f5 100644 --- a/sncf-station/package.json +++ b/sncf-station/package.json @@ -7,6 +7,9 @@ "@emotion/styled": "^11.11.0", "@mui/material": "^5.15.6", "@mui/x-date-pickers": "^6.19.2", + "@tanstack/query-sync-storage-persister": "^5.18.0", + "@tanstack/react-query": "^5.18.0", + "@tanstack/react-query-persist-client": "^5.18.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/sncf-station/src/App.js b/sncf-station/src/App.js index f50e521..97d9f4b 100644 --- a/sncf-station/src/App.js +++ b/sncf-station/src/App.js @@ -6,6 +6,9 @@ import {frFR, LocalizationProvider} from "@mui/x-date-pickers" import {AdapterDayjs} from "@mui/x-date-pickers/AdapterDayjs" import 'dayjs/locale/fr' import './App.css' +import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; +import {createSyncStoragePersister} from "@tanstack/query-sync-storage-persister"; +import {persistQueryClient} from "@tanstack/react-query-persist-client"; function App() { const router = createBrowserRouter([ @@ -40,11 +43,32 @@ function App() { [prefersDarkMode], ); + const queryClient = new QueryClient({ + defaultOptions: { + queries: { + gcTime: 1000 * 60 * 60 * 24, // 3 hours + staleTime: 1000 * 60 * 60 * 3, // 3 hours + notifyOnChangeProps: ['data', 'error'], + }, + }, + }) + + const localStoragePersister = createSyncStoragePersister({ + storage: window.localStorage, + }) + + persistQueryClient({ + queryClient, + persister: localStoragePersister, + }) + return <> - + + + diff --git a/sncf-station/src/Station.js b/sncf-station/src/Station.js index fed9001..ec636c9 100644 --- a/sncf-station/src/Station.js +++ b/sncf-station/src/Station.js @@ -1,9 +1,10 @@ import {useParams, useSearchParams} from "react-router-dom" import TrainsTable from "./TrainsTable" -import {useEffect, useState} from "react"; -import {Box, Button, FormControl, FormControlLabel, FormGroup, FormLabel} from "@mui/material"; -import {DatePicker, DateTimePicker, TimePicker} from "@mui/x-date-pickers"; +import {useState} from "react"; +import {Box, Button, FormLabel} from "@mui/material"; +import {DatePicker, TimePicker} from "@mui/x-date-pickers"; import dayjs from "dayjs"; +import {useQuery, useQueryClient} from "@tanstack/react-query"; function DateTimeSelector({date, time}) { return <> @@ -20,7 +21,6 @@ function DateTimeSelector({date, time}) { function Station() { let {stopId} = useParams() - let [stop, setStop] = useState({'name': "Chargement…"}) let [searchParams, _setSearchParams] = useSearchParams() const now = new Date() let dateNow = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` @@ -28,13 +28,14 @@ function Station() { let [date, setDate] = useState(searchParams.get('date') || dateNow) let [time, setTime] = useState(searchParams.get('time') || timeNow) - useEffect(() => { - fetch(`http://localhost:8000/api/gtfs/stop/${stopId}/`) - .then(response => response.json()) - .then(data => { - setStop(data) - }) - }, [stopId]) + useQueryClient() + const stopQuery = useQuery({ + queryKey: ['stop', stopId], + queryFn: () => fetch(`http://localhost:8000/api/gtfs/stop/${stopId}/`) + .then(response => response.json()), + enabled: !!stopId, + }) + const stop = stopQuery.data ?? {name: "Chargement…"} if (time === timeNow) { setInterval(() => { @@ -43,7 +44,7 @@ function Station() { let timeNow = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}` setDate(dateNow) setTime(timeNow) - }, 60000) + }, 5000) } return ( diff --git a/sncf-station/src/TrainsTable.js b/sncf-station/src/TrainsTable.js index e82b64f..504f1b9 100644 --- a/sncf-station/src/TrainsTable.js +++ b/sncf-station/src/TrainsTable.js @@ -1,4 +1,3 @@ -import {useEffect, useState} from "react" import { Box, styled, @@ -11,6 +10,8 @@ import { Typography } from "@mui/material" import {CSSTransition, TransitionGroup} from 'react-transition-group' +import {useQueries, useQuery, useQueryClient} from "@tanstack/react-query"; +import {useEffect, useState} from "react"; const StyledTableRow = styled(TableRow)(({ theme, tableType }) => ({ 'tbody &:nth-of-type(odd)': { @@ -49,27 +50,37 @@ function TrainsTableHeader({tableType}) { } function TrainsTableBody({stop, date, time, tableType}) { - const [trains, setTrains] = useState([]) + const queryClient = useQueryClient() - useEffect(() => { - if (stop.id !== undefined) { - let validTrains = trains.filter(train => { - if (tableType === "departures") - return `${train.departure_date}T${train.departure_time_24h}` >= `${date}T${time}` - else - return `${train.arrival_date}T${train.arrival_time_24h}` >= `${date}T${time}` - }) - if (trains.length > 0 && validTrains.length === trains.length) - return - console.log(`${trains.length - validTrains.length} trains deleted`) - fetch(`http://localhost:8000/api/station/next_${tableType}/?stop_id=${stop.id}&date=${date}&time=${time}&offset=${validTrains.length}&limit=${20 - validTrains.length}`) + let filterTime = (train) => { + if (train.departure_time === "04:56:00") + return false + if (tableType === "departures") + return `${train.departure_date}T${train.departure_time_24h}` >= `${date}T${time}` + else + return `${train.arrival_date}T${train.arrival_time_24h}` >= `${date}T${time}` + } + + function updateTrains() { + return fetch(`http://localhost:8000/api/station/next_${tableType}/?stop_id=${stop.id}&date=${date}&time=${time}&offset=${0}&limit=${20}`) .then(response => response.json()) .then(data => data.results) - .then(data => { - setTrains(trains => [...validTrains, ...data]) - }) - } - }, [stop, tableType, date, time]) + .then(data => [...data]) + } + + const trainsQuery = useQuery({ + queryKey: ['trains', stop.id, tableType], + queryFn: updateTrains, + enabled: !!stop.id, + }) + const trains = trainsQuery.data ?? [] + + useEffect(() => { + let validTrains = trains?.filter(filterTime) ?? [] + console.log(validTrains.length) + if (trains?.length > 0 && validTrains.length <= trains?.length) + queryClient.invalidateQueries({queryKey: ['trains', stop.id, tableType]}) + }, [stop.id, tableType, date, time]) let table_rows = trains.map((train) => @@ -85,64 +96,51 @@ function TrainsTableBody({stop, date, time, tableType}) { } function TrainRow({train, tableType}) { - const [trip, setTrip] = useState({}) - const [route, setRoute] = useState({}) - const [stopTimes, setStopTimes] = useState([]) - const [trainType, setTrainType] = useState("") + const tripQuery = useQuery({ + queryKey: ['trip', train.trip], + queryFn: () => fetch(`http://localhost:8000/api/gtfs/trip/${train.trip}/`) + .then(response => response.json()), + enabled: !!train.trip, + }) + const trip = tripQuery.data ?? {} - useEffect(() => { - if (train.trip !== undefined) { - fetch(`http://localhost:8000/api/gtfs/trip/${train.trip}/`) + const routeQuery = useQuery({ + queryKey: ['route', trip.route], + queryFn: () => fetch(`http://localhost:8000/api/gtfs/route/${trip.route}/`) + .then(response => response.json()), + enabled: !!trip.route, + }) + const route = routeQuery.data ?? {} + const trainType = getTrainType(train, route) + + const stopTimesQuery = useQuery({ + queryKey: ['stop_times', trip.id], + queryFn: () => fetch(`http://localhost:8000/api/gtfs/stop_time/?trip=${trip.id}&order=stop_sequence&limit=1000`) .then(response => response.json()) - .then(t => { - t.stop_times = [] - setTrip(t) - }) - } - }, [train.trip]) + .then(data => data.results), + enabled: !!trip.id, + }) + const stopTimes = stopTimesQuery.data ?? [] + const stopIds = stopTimes.map(stop_time => stop_time.stop) - useEffect(() => { - if (trip.route !== undefined) { - fetch(`http://localhost:8000/api/gtfs/route/${trip.route}/`) - .then(response => response.json()) - .then(data => { - setRoute(data) - }) - } - }, [trip.route]) + const stopQueries = useQueries({ + queries: stopIds.map(stopId => ({ + queryKey: ['stop', stopId], + queryFn: () => fetch(`http://localhost:8000/api/gtfs/stop/${stopId}/`) + .then(response => response.json()), + enabled: !!stopId, + })), + }) + const stops = stopTimes.map(((stopTime, i) => ({...stopTime, stop: stopQueries[i]?.data ?? {"name": "…"}}))) ?? [] - useEffect(() => { - if (route !== undefined) { - setTrainType(getTrainType(train, route)) - } - }, [train, route]); + let headline = stops[tableType === "departures" ? stops.length - 1 : 0]?.stop ?? {name: "Chargement…"} - useEffect(() => { - if (trip.route !== undefined) { - fetch(`http://localhost:8000/api/gtfs/stop_time/?trip=${trip.id}&order=stop_sequence&limit=1000`) - .then(response => response.json()) - .then(data => data.results) - .then(stop_times => { - Promise.all(stop_times.map(stop_time => - fetch(`http://localhost:8000/api/gtfs/stop/${stop_time.stop}/`).then(response => response.json()) - )).then(stops => { - setStopTimes(stop_times.map((stop_time, index) => { - stop_time.stop = stops[index] - return stop_time - })) - }) - }) - } - }, [trip.route]) - - let headline = stopTimes[tableType === "departures" ? stopTimes.length - 1 : 0]?.stop ?? {name: "Chargement…"} - - let stops_filter + let stopsFilter if (tableType === "departures") - stops_filter = (stop_time) => stop_time.stop_sequence > train.stop_sequence && stop_time.drop_off_type === 0 + stopsFilter = (stop_time) => stop_time.stop_sequence > train.stop_sequence && stop_time.drop_off_type === 0 else - stops_filter = (stop_time) => stop_time.stop_sequence < train.stop_sequence && stop_time.pickup_type === 0 - let stops_names = stopTimes.filter(stops_filter).map(stop_time => stop_time?.stop.name ?? "").join(", ") + stopsFilter = (stop_time) => stop_time.stop_sequence < train.stop_sequence && stop_time.pickup_type === 0 + let stopsNames = stops.filter(stopsFilter).map(stopTime => stopTime?.stop.name ?? "").join(", ") ?? "" return <> @@ -176,8 +174,8 @@ function TrainRow({train, tableType}) { - {headline.name} - {stops_names} + {headline.name} + {stopsNames}