11import { fetch , addTask } from 'domain-task' ;
2- import { typeName , isActionType , Action , Reducer } from 'redux-typed ' ;
3- import { ActionCreator } from './' ;
2+ import { Action , Reducer , ThunkAction , ActionCreator } from 'redux' ;
3+ import { AppThunkAction } from './' ;
44
55// -----------------
66// STATE - This defines the type of data maintained in the Redux store.
@@ -21,57 +21,70 @@ export interface WeatherForecast {
2121// -----------------
2222// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
2323// They do not themselves have any side-effects; they just describe something that is going to happen.
24- // Use @typeName and isActionType for type detection that works even after serialization/deserialization.
2524
26- @typeName ( "REQUEST_WEATHER_FORECASTS" )
27- class RequestWeatherForecasts extends Action {
28- constructor ( public startDateIndex : number ) {
29- super ( ) ;
30- }
25+ interface RequestWeatherForecastsAction {
26+ type : 'REQUEST_WEATHER_FORECASTS' ,
27+ startDateIndex : number ;
3128}
3229
33- @typeName ( "RECEIVE_WEATHER_FORECASTS" )
34- class ReceiveWeatherForecasts extends Action {
35- constructor ( public startDateIndex : number , public forecasts : WeatherForecast [ ] ) {
36- super ( ) ;
37- }
30+ interface ReceiveWeatherForecastsAction {
31+ type : 'RECEIVE_WEATHER_FORECASTS' ,
32+ startDateIndex : number ;
33+ forecasts : WeatherForecast [ ]
3834}
3935
36+ // Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
37+ // declared type strings (and not any other arbitrary string).
38+ type KnownAction = RequestWeatherForecastsAction | ReceiveWeatherForecastsAction ;
39+
4040// ----------------
4141// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
4242// They don't directly mutate state, but they can have external side-effects (such as loading data).
4343
4444export const actionCreators = {
45- requestWeatherForecasts : ( startDateIndex : number ) : ActionCreator => ( dispatch , getState ) => {
45+ requestWeatherForecasts : ( startDateIndex : number ) : AppThunkAction < KnownAction > => ( dispatch , getState ) => {
4646 // Only load data if it's something we don't already have (and are not already loading)
4747 if ( startDateIndex !== getState ( ) . weatherForecasts . startDateIndex ) {
4848 let fetchTask = fetch ( `/api/SampleData/WeatherForecasts?startDateIndex=${ startDateIndex } ` )
4949 . then ( response => response . json ( ) )
5050 . then ( ( data : WeatherForecast [ ] ) => {
51- dispatch ( new ReceiveWeatherForecasts ( startDateIndex , data ) ) ;
51+ dispatch ( { type : 'RECEIVE_WEATHER_FORECASTS' , startDateIndex : startDateIndex , forecasts : data } ) ;
5252 } ) ;
5353
5454 addTask ( fetchTask ) ; // Ensure server-side prerendering waits for this to complete
55- dispatch ( new RequestWeatherForecasts ( startDateIndex ) ) ;
55+ dispatch ( { type : 'REQUEST_WEATHER_FORECASTS' , startDateIndex : startDateIndex } ) ;
5656 }
5757 }
5858} ;
5959
6060// ----------------
6161// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
62+
6263const unloadedState : WeatherForecastsState = { startDateIndex : null , forecasts : [ ] , isLoading : false } ;
63- export const reducer : Reducer < WeatherForecastsState > = ( state , action ) => {
64- if ( isActionType ( action , RequestWeatherForecasts ) ) {
65- return { startDateIndex : action . startDateIndex , isLoading : true , forecasts : state . forecasts } ;
66- } else if ( isActionType ( action , ReceiveWeatherForecasts ) ) {
67- // Only accept the incoming data if it matches the most recent request. This ensures we correctly
68- // handle out-of-order responses.
69- if ( action . startDateIndex === state . startDateIndex ) {
70- return { startDateIndex : action . startDateIndex , forecasts : action . forecasts , isLoading : false } ;
71- }
64+
65+ export const reducer : Reducer < WeatherForecastsState > = ( state : WeatherForecastsState , action : KnownAction ) => {
66+ switch ( action . type ) {
67+ case 'REQUEST_WEATHER_FORECASTS' :
68+ return {
69+ startDateIndex : action . startDateIndex ,
70+ forecasts : state . forecasts ,
71+ isLoading : true
72+ } ;
73+ case 'RECEIVE_WEATHER_FORECASTS' :
74+ // Only accept the incoming data if it matches the most recent request. This ensures we correctly
75+ // handle out-of-order responses.
76+ if ( action . startDateIndex === state . startDateIndex ) {
77+ return {
78+ startDateIndex : action . startDateIndex ,
79+ forecasts : action . forecasts ,
80+ isLoading : false
81+ } ;
82+ }
83+ break ;
84+ default :
85+ // The following line guarantees that every action in the KnownAction union has been covered by a case above
86+ const exhaustiveCheck : never = action ;
7287 }
73-
74- // For unrecognized actions (or in cases where actions have no effect), must return the existing state
75- // (or default initial state if none was supplied)
88+
7689 return state || unloadedState ;
7790} ;
0 commit comments