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}
>