For every action that is called on a reducer, the new state is returned.
From the example code in the question, state is just a list of instruments.
There's no index, so the only way to check if an instrument is in the list is to search the whole list.
But what if your state was a dictionary? Furthermore, what if you kept a list of indexes seperate to the dictionary?
your state type is this:
export interface OfferState {
ids: string[];
entities: { [id: string]: IOffer };
};
Any time an action is executed, the new state is returned. It is an important concept in Redux, because state can never be mutated directly. You're actually best strictly enforcing this when you compose your reducer: (say you've got you "offers reducer" and another reducer, you combine them to one with compose:
> export default compose(storeFreeze, combineReducers) ({ oether:
> otherReducer, offers: offersReducer });
Its easy to do things wrong in Redux - but using storeFreeze will throw up an error if you try to mutate the state directly. The point is that actions change state, and make a new state. They don't change the existing state - it lets us undo/redo... etc.
Using your example above I would use this as my Offer's reducer:
export interface OfferState {
ids: string[];
entities: { [id: string]: IOffer };
};
export default function(state = initialState, action: Action): OfferState {
switch(action.type){
case OfferActions.INSERT:
const offer : IOffer = action.payload;
return {
ids: [ ...state.ids, action.payload.Instrument ],
entities: Object.assign({}, state.entities, { [action.payload.Instrument]: action.payload})
};
case OfferActions.UPDATE:
return {
ids: [...state.ids],
entities: Object.assign({}, state.entities, { [action.payload.Instrument]: action.payload})
}
default:
return state;
}
}
note that changes are made to a temporary state via object.assign (deep copy) and then the new state is returned.
The other answer to the question was a bit confusing. It went into the detail of how to combine different reducers, but it didn't make much sense to me.
in your reducers/index.ts you should have a type:
export interface AppState {
otherReducer: OtherReducer;
offers: fromOffersReducer.OfferState;
}
inside this index.ts, you should have functions that get the reducers:
export function getOfferState() {
return (state$: Observable<AppState>) => state$
.select(s => s.offers);
}
export function getOtherReducer() {
return (state$ : Observable<AppState>) => state$
.select(s => s.otherReducer)
}
inside our offerReducer and our otherReducer, we define functions that can query the data we need. These are anonymous functions, that are not linked to anything at present, but we will link them later (to the getReducerFunctions).
examples of these functions:
export function getOfferEntities() {
return (state$: Observable<OfferState>) => state$
.select(s => s.entities);
};
export function getOffer(id: string) {
return (state$: Observable<OfferState>) => state$
.select(s => s.entities[id]);
}
this does nothing. unless we apply it to some useful data (e.g. the offersRedeucer) that we made ealier, and we combine the two like this:
import offersReducer, * as fromOffersReducer from './OffersReducer';
export function getOfferEntities() {
return compose(fromOffersReducer.getOfferEntities(), getOfferState());
}
export function getOffer(instrument:string) {
return compose(fromOffersReducer.getOffer(instrument), getOfferState());
}