I'm working on an app in React with Redux, Saga, and using Typescript.
The app structure is setup such that each primary top level container component has a corresponding file in a Store directory for defining it's action creators, reducers, and sagas.
When the app starts all of the reducers are combined from the Store files, and the Sagas are combined into a common rootSaga function.
Everything works fine except now that I am trying to use a selector to load some state properties into one of my Sagas. I am not getting any errors but my selector function isn't returning my state values.
If I try to use the getState() function in my Store file I do get a Typescript error 'Cannot find the name getState'.
Clearly I'm not including the correct library in my Store file or I'm not calling the state function by the correct namespace but I can't figure out what's wrong.
I switched from Thunk middleware to using Saga. When Thunk was wired in to the app I was able to use getState in the Store file.
This is the Store file with my action creators, reducers, and sagas.
My selector function is in the file as well (export const getVersionQueueFilters):
import { fetch, addTask } from 'domain-task';
import { Action, Reducer, ActionCreator } from 'redux';
import { takeLatest, takeEvery } from "redux-saga"
import { call, put, take, race, select } from "redux-saga/effects"
import * as moment from 'moment';
// -----------------
// STATE - This defines the type of data maintained in the Redux store.
export interface ASVersionQueueState {
queuedVersions: QueuedVersion[];
versionQueueFilter: VersionQueueFilter;
eventsFilterList: SelectListItem[];
employeesFilterList: SelectListItem[];
gridIsLoading: boolean;
versionQueueRefresh: boolean;
error: boolean;
}
export interface QueuedVersion {
VersionCode: string;
VersionQualifier: string;
VersionID: string;
ProductID: string;
PieceName: string;
PrintClass: string;
FirstInhomeDate: string;
AccountID: string;
AccountExecutive: string;
AccountManager: string;
ArtManager: string;
AdUID: string;
Status: string;
Queue: string;
DueDateOverride: string;
IsLocked: string;
}
export interface VersionQueueFilter {
StartDate: string;
EndDate: string;
PieceType: Array<string>;
EventType: Array<string>;
EventID: string;
Employee: string;
}
export interface SelectListItem {
OptionName: string;
OptionVal: string;
}
export let DefaultVersionQueueFilter = {
StartDate: moment().subtract(30, 'days').format('YYYY-MM-DD'),
EndDate: moment().format('YYYY-MM-DD'),
PieceType: ['impactpc'],
EventType: ['special'],
EventID: '',
Employee: '12345'
}
// Version Queue polling delay value
let versionQueuePollDelay: number = 10000; // Delay in milliseconds
// -----------------
// ACTIONS - These are serializable (hence replayable) descriptions of state transitions.
// They do not themselves have any side-effects; they just describe something that is going to happen.
// Use @typeName and isActionType for type detection that works even after serialization/deserialization.
interface PollVersionsAction {
type: 'POLL_VERSIONS';
versionQueueFilter: VersionQueueFilter;
versionQueueRefresh: boolean;
}
interface PollRequestVersionsAction {
type: 'POLL_REQUEST_VERSIONS';
versionQueueFilter: VersionQueueFilter;
versionQueueRefresh: boolean;
}
interface PollRequestVersionsSuccessAction {
type: 'POLL_REQUEST_VERSIONS_SUCCESS';
versionQueueFilter: VersionQueueFilter;
receivedVersions: QueuedVersion[];
versionQueueRefresh: boolean;
}
interface PollRequestVersionsErrorAction {
type: 'POLL_REQUEST_VERSIONS_ERROR';
}
// Declare a 'discriminated union' type. This guarantees that all references to 'type' properties contain one of the
// declared type strings (and not any other arbitrary string).
type KnownAction = PollVersionsAction | PollRequestVersionsSuccessAction | PollRequestVersionsErrorAction;
// ----------------
// ACTION CREATORS - These are functions exposed to UI components that will trigger a state transition.
// They don't directly mutate state
export const actionCreators = {
pollVersions: () => {
return { type: 'POLL_VERSIONS', versionQueueFilter: getVersionQueueFilters, versionQueueRefresh: true }
},
pollRequestVersions: (versionQueueFilter: VersionQueueFilter, versionQueueRefresh: boolean) => {
return { type: 'POLL_REQUEST_VERSIONS', versionQueueFilter: versionQueueFilter, versionQueueRefresh: versionQueueRefresh }
},
pollRequestVersionsSuccess: (versionQueueFilter: VersionQueueFilter, versionQueueRefresh: boolean, data: QueuedVersion[]) => {
return { type: 'POLL_REQUEST_VERSIONS_SUCCESS', versionQueueFilter: versionQueueFilter, receivedVersions: data, versionQueueRefresh: versionQueueRefresh }
},
pollRequestVersionsError: () => {
return { type: 'POLL_REQUEST_VERSIONS_ERROR' }
}
};
// ----------------
// REDUCER - For a given state and action, returns the new state. To support time travel, this must not mutate the old state.
const unloadedState: ASVersionQueueState = { gridIsLoading: false, versionQueueRefresh: false, queuedVersions: [], versionQueueFilter: DefaultVersionQueueFilter, eventsFilterList: [], employeesFilterList: [], error: false };
export const reducer: Reducer<ASVersionQueueState> = (state: ASVersionQueueState, incomingAction: Action) => {
const action = incomingAction as KnownAction;
switch (action.type) {
case 'POLL_VERSIONS':
return {
...state,
versionQueueFilter: action.versionQueueFilter,
versionQueueRefresh: action.versionQueueRefresh,
gridIsLoading: true
}
case 'POLL_REQUEST_VERSIONS_SUCCESS':
// Only accept the incoming data if it matches the most recent request. This ensures we correctly
// handle out-of-order responses.
if (action.versionQueueFilter === state.versionQueueFilter && action.versionQueueRefresh === state.versionQueueRefresh) {
return {
...state,
queuedVersions: action.receivedVersions,
versionQueueRefresh: action.versionQueueRefresh,
gridIsLoading: false
}
}
break;
case 'POLL_REQUEST_VERSIONS_ERROR':
return {
...state,
error: true
}
default:
// The following line guarantees that every action in the KnownAction union has been covered by a case above
const exhaustiveCheck: never = action;
}
return state || unloadedState;
};
// Sagas
// Saga Watchers
export const sagas = [
takeEvery('POLL_VERSIONS', fetchPollVersionsAsync)
]
// Selector Function
export const getVersionQueueFilters = (store: ASVersionQueueState) => store.versionQueueFilter;
// Utility function to delay effects
export function delay(delayMS: number) {
const promise = new Promise(resolve => {
setTimeout(() => resolve(true), delayMS)
});
return promise;
}
export function* versionPoller() {
const versionQueueFilters = yield select(getVersionQueueFilters);
try {
yield call(delay, versionQueuePollDelay);
yield put(actionCreators.pollVersions() );
} catch (error) {
// cancellation error
return;
}
}
export function* watchVersionPoller() {
while (true) {
yield take('POLL_REQUEST_VERSIONS_SUCCESS');
yield call(versionPoller);
}
}
export function* fetchPollVersionsAsync(action: PollVersionsAction) {
try {
yield put(actionCreators.pollRequestVersions(action.versionQueueFilter, action.versionQueueRefresh));
const data = yield call(() => {
return fetch('api/Versions')
.then(res => res.json())
}
);
yield put(actionCreators.pollRequestVersionsSuccess(action.versionQueueFilter, action.versionQueueRefresh, data));
} catch (error) {
yield put(actionCreators.pollRequestVersionsError());
}
}
The selector is used in the saga function "versionPoller()".
Basically I'm polling my API for any updated data but it requires passing at least a default set of filter values. I want to use the filter values currently in state for that.
I have also tried defining my selector function as:
export const getVersionQueueFilters = getState().ASVersionQueueState.versionQueueFilter;
When I do that I get the error 'cannot find the name getState'.
Any idea what I'm doing wrong?