1
1
import { 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 './' ;
4
4
5
5
// -----------------
6
6
// STATE - This defines the type of data maintained in the Redux store.
@@ -21,57 +21,70 @@ export interface WeatherForecast {
21
21
// -----------------
22
22
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
23
23
// 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.
25
24
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 ;
31
28
}
32
29
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 [ ]
38
34
}
39
35
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
+
40
40
// ----------------
41
41
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
42
42
// They don't directly mutate state, but they can have external side-effects (such as loading data).
43
43
44
44
export const actionCreators = {
45
- requestWeatherForecasts : ( startDateIndex : number ) : ActionCreator => ( dispatch , getState ) => {
45
+ requestWeatherForecasts : ( startDateIndex : number ) : AppThunkAction < KnownAction > => ( dispatch , getState ) => {
46
46
// Only load data if it's something we don't already have (and are not already loading)
47
47
if ( startDateIndex !== getState ( ) . weatherForecasts . startDateIndex ) {
48
48
let fetchTask = fetch ( `/api/SampleData/WeatherForecasts?startDateIndex=${ startDateIndex } ` )
49
49
. then ( response => response . json ( ) )
50
50
. then ( ( data : WeatherForecast [ ] ) => {
51
- dispatch ( new ReceiveWeatherForecasts ( startDateIndex , data ) ) ;
51
+ dispatch ( { type : 'RECEIVE_WEATHER_FORECASTS' , startDateIndex : startDateIndex , forecasts : data } ) ;
52
52
} ) ;
53
53
54
54
addTask ( fetchTask ) ; // Ensure server-side prerendering waits for this to complete
55
- dispatch ( new RequestWeatherForecasts ( startDateIndex ) ) ;
55
+ dispatch ( { type : 'REQUEST_WEATHER_FORECASTS' , startDateIndex : startDateIndex } ) ;
56
56
}
57
57
}
58
58
} ;
59
59
60
60
// ----------------
61
61
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
62
+
62
63
const 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 ;
72
87
}
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
+
76
89
return state || unloadedState ;
77
90
} ;
0 commit comments