Skip to content

Instantly share code, notes, and snippets.

@hydrosquall
Created June 28, 2019 18:01
Show Gist options
  • Save hydrosquall/c2c113f12565a3eb83180b0b2304d1ca to your computer and use it in GitHub Desktop.
Save hydrosquall/c2c113f12565a3eb83180b0b2304d1ca to your computer and use it in GitHub Desktop.
typesafe action reducers
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 })
);
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