Created
June 28, 2019 18:01
-
-
Save hydrosquall/c2c113f12565a3eb83180b0b2304d1ca to your computer and use it in GitHub Desktop.
typesafe action reducers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createAction } from "typesafe-actions"; | |
import { TrainStop, TrainStation } from './types'; | |
// Actions - no need for constants to be used elsewhere! | |
const SET_SELECTED_ROUTE = "SET_SELECTED_ROUTE"; | |
const SET_STOPS = "SET_STOPS"; | |
const SET_STATIONS = "SET_STATIONS"; | |
const SET_MAX_TRIPS = "SET_MAX_TRIPS"; | |
const SET_ROUTE_DATA = "SET_ROUTE_DATA"; // load stops and stations simultaneously | |
const SET_SELECTED_STATIONS = "SET_SELECTED_STATIONS"; // load stops and stations simultaneously | |
const SET_TIME_RANGE = "SET_TIME_RANGE"; // load stops and stations simultaneously | |
// Action creators | |
export const setSelectedRoute = createAction( | |
SET_SELECTED_ROUTE, | |
action => (routeId: string) => | |
action({ | |
routeId | |
}) | |
); | |
export const setStops = createAction( | |
SET_STOPS, | |
action => (stops: TrainStop[]) => action({ stops }) | |
); | |
export const setStations = createAction( | |
SET_STATIONS, | |
action => (stations: TrainStation[]) => action({ stations }) | |
); | |
export const setMaxTrips = createAction( | |
SET_MAX_TRIPS, | |
action => (maxTrips: number) => action({ maxTrips }) | |
); | |
export const setRouteData = createAction( | |
SET_ROUTE_DATA, | |
action => (stops: TrainStop[], stations: TrainStation[]) => action({ stops, stations }) | |
); | |
export const setSelectedStations = createAction( | |
SET_SELECTED_STATIONS, | |
action => (stations: TrainStation[]) => action({ stations }) | |
); | |
export const setTimeRange = createAction( | |
SET_TIME_RANGE, | |
action => (start: Date, end: Date) => action({ start, end }) | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { TrainStop, TrainStation } from './types'; | |
import { getType, ActionType } from 'typesafe-actions'; | |
import { nest } from 'd3-collection'; // deprecated, replace with a better data transformation tool | |
import { timeParse } from "d3-time-format"; | |
import { createSelector } from 'reselect'; | |
import { some } from 'lodash'; | |
import * as actions from './actions'; // in future: split into multiple | |
import { createStructuredSelector } from 'reselect'; | |
type RootAction = ActionType<typeof actions>; | |
interface RootState { | |
stops: TrainStop[], | |
stations: TrainStation[], | |
maxTrips: number, | |
selectedRoute: string, | |
// Selections, default to everyone | |
selectedStations: TrainStation[], | |
timeRange: { | |
start: Date; | |
end: Date; | |
} | |
} | |
const inputTimeParse = timeParse("%H:%M:%S"); | |
const initialState: RootState = { | |
stops: [], | |
stations: [], | |
maxTrips: 2000, // Number of line segments to limit to initially, for performance | |
selectedRoute: "3", // Start with New Haven Line - route-id | |
selectedStations: [], | |
timeRange: { | |
start: inputTimeParse("03:50:00") || new Date(), | |
end: inputTimeParse("23:59:59") || new Date() | |
} | |
}; | |
// TODO: immer | |
const rootReducer = (state: RootState = initialState, action: RootAction) => { | |
switch (action.type) { | |
case getType(actions.setStops): | |
return {...state, stops: action.payload.stops } // autocompleted! | |
case getType(actions.setStations): | |
// when route is changed, select all of its stations by default. | |
return { ...state, stations: action.payload.stations, selectedStations: action.payload.stations } | |
case getType(actions.setMaxTrips): | |
return { ...state, maxTrips: action.payload.maxTrips } | |
case getType(actions.setSelectedRoute): | |
return { ...state, selectedRoute: action.payload.routeId } | |
case getType(actions.setRouteData): | |
return { ...state, | |
stops: action.payload.stops, | |
stations: action.payload.stations, | |
selectedStations: action.payload.stations | |
} | |
// user selection | |
case getType(actions.setSelectedStations): | |
return { | |
...state, | |
selectedStations: action.payload.stations | |
} | |
case getType(actions.setTimeRange): | |
return { | |
...state, | |
timeRange: { | |
start: action.payload.start, | |
end: action.payload.end | |
} | |
} | |
default: | |
return state; | |
} | |
}; | |
// selectors | |
// const getStops = (state: RootState) => state.stops; | |
const getStations = (state: RootState) => state.stations; | |
const getMaxTrips = (state: RootState) => state.maxTrips; | |
const getSelectedRoute = (state: RootState) => state.selectedRoute; | |
const getSelectedStations = (state: RootState) => state.selectedStations.map(station => station.stop_id); | |
const getTimeRange = (state: RootState) => [state.timeRange.start, state.timeRange.end] | |
const getStopsByTrip = (state: RootState) => { | |
const stopsByTrip = nest() | |
.key((d: any) => `${d.trip_id}`) // actually: just trip_id | |
.entries(state.stops) | |
.slice(0, state.maxTrips); | |
return stopsByTrip; | |
}; | |
// Make sets of which trips stop in which stations | |
const getStationsByTrip = createSelector( | |
getStopsByTrip, | |
(stopsByTrip) => { | |
return stopsByTrip.map((trip: any) => { | |
return new Set((trip.values as TrainStop[]).map((stop => stop.stop_id))) | |
} | |
)} | |
); | |
// TODO: filter this more efficiently | |
const getStopsByTripFiltered = createSelector( | |
getStopsByTrip, | |
getStationsByTrip, | |
getSelectedStations, | |
(stopsByTrip, stationsByTrip, selectedStations) => { | |
return stopsByTrip.filter((_, i) => { | |
return some(selectedStations, (station) => stationsByTrip[i].has(station) ) | |
}) | |
} | |
); | |
interface ExplorerSelection { | |
stations: TrainStation[], | |
maxTrips: number, | |
selectedRoute: string, | |
stopsByTrip: any[], | |
timeRange: Date[] | |
} | |
// Component selector - avoids need for exporting micro selectors | |
export const explorerSelector = createStructuredSelector<RootState, ExplorerSelection> ({ | |
stations: getStations, | |
maxTrips: getMaxTrips, | |
selectedRoute: getSelectedRoute, | |
stopsByTrip: getStopsByTripFiltered, | |
timeRange: getTimeRange | |
}) | |
export default rootReducer; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment