Reducers & Store
Just like Redux, at the heart of Gambit is your single app store. This is created in exactly the same way as you would with Redux:
const store = createStore(reducers, initialState, middleware);
For now, let's focus on reducers. These are functions that take an action that has been dispatched and then change the applications state in some way. Typically, reducers are nested objects that reference certain areas of an applications state:
userReducer = { allUsers, currentUser, userCount };
tweetReducer = { allTweets, myTweets, tweetCount };
appReducer = { userReducer, tweetReducer };
As with plain Redux, what you pass in as the first argument to createStore
is the appReducer.
Let's start creating our first Gambit App by creating the reducer we'll need to respond to API calls to Github. Create a file src/reducers/user.js
.
import { createReducer } from 'gambit';
import { fromJS, Map } from 'immutable';
import UserConstants from '../constants/User';
export default createReducer({
userSearches: [new Map({}), {
[UserConstants.GET_USER_SEARCH_DONE]: ({
body: { items },
query,
}, prevState) => {
return prevState.set(query, fromJS(items));
},
}],
searching: [false, {
[UserConstants.GET_USER_SEARCH_STARTING]: true,
[
UserConstants.GET_USER_SEARCH_DONE +
UserConstants.GET_USER_SEARCH_FAILED
]: false,
}],
searchFailed: [new Map({}), {
[UserConstants.GET_USER_SEARCH_FAILED]: ({
query,
}, prevState) => {
return prevState.set(query, true);
},
[
UserConstants.GET_USER_SEARCH_STARTING +
UserConstants.GET_USER_SEARCH_DONE
]: ({
query,
}, prevState) => {
return prevState.set(query, false);
},
}],
});
You'll get some linter warnings about files that don't exist yet but that's fine. This is the userReducer that will store the users that have been returned by the Github API when we search.
We have created three properties that will hold the following data:
userSearches
: This will keep a record of all searches indexed by the querysearching
: Whether or not searching is currently happeningsearchFailed
: Record of which searches have failed, indexed by the query
As you can see createReducer
is a helper method provided by Gambit which has the following signature:
createContainer(reducerProps = {}, opts = {})
reducerProps = {
propertyName: [initialState, {
ACTION_TO_RESPOND_TO: (actionArgs = {}, previousState) => newState,
},
}
opts = {
hearGeneral = false,
asImmutable = true,
}
Reducer Props and Immutable
Each reducer property (in the above example userSearches
, searching
, searchFailed
) will be accessed from the store via state.reducerName.reducerProp
or for instance state.user.searching
.
In actual fact, they should be accessed by state.reducerName.get(reducerProp)
unless you have set isImmutable
to false
.
isImmutable
is the default and is recommended (along with using immutable initial state values for each reducer property).
Responding to Multiple Actions
You can also see that it is possible to respond to multiple actions for the same reducer property:
[
UserConstants.GET_USER_SEARCH_DONE +
UserConstants.GET_USER_SEARCH_FAILED
]
Remember, [x]
within an ES6 object is not an array but is evaluating the internal expression to create a key. That means it's [FOO + BAR]
and not [FOO, BAR]
.
NB: You should only use the +
operator within a reducer when you've created your constants with createConstants
, otherwise hijinks may ensue