Dedicated to Chris Cornell R.I.P
A Redux library which lets you remove most of the boilerplate associated with writing a Redux application, yet allows you to customize it completely without loosing control.
The library receives an initial state and generates actions, selectors and a super reducer which does all the heavy lifting for you so you can focus on the most important thing - your app.
npm install --save redux-cornell
Before creating your root reducer, initialize reduxCornell with your initial state:
// `src/redux/reducers/index.js`
// import ReduxCornell
import reduxCornell from 'redux-cornell';
import { combineReducers } from 'redux';
// The function expects an object with the `initialState` key.
// In this example 'episodes' and 'showInfo' will become the "reducers".
const { selectors, actions, superReducer } = reduxCornell({
initialState: {
episodes: {
loaded: false,
data: [],
expanded: {}
},
showInfo: {
visible: false
}
}
});
// You will get default actions and selectors (covered below).
// Export them so they are available across your app.
export { selectors, actions };
// Add the superReducer to your rootReducer`
const rootReducer = combineReducers({ superReducer });
// Export the rootReducer like you usually do.
export default rootReducer;
The selectors are automatically generated from the initialState. They are built in the following format:
get<Reducer><Property>
The selectors accept only one parameter - the state.
So from the initialState in our example, we will get the following selectors:
getEpisodesLoaded
getEpisodesData
getEpisodesExpanded
getShowInfoVisible
Then, in your mapStateToProps you can use them like this:
import { selectors } from '../redux/reducers';
const mapStateToProps = (state) => ({
episodes: selectors.getEpisodesData(state)
});
The actions are automatically generated from the initialState. Redux Cornell will analyze your initialState and generate actions according to the property's initial value.
If the property's initial value is a boolean you will get an action in the following format: toggle<Reducer><Property>
So in our example, you will get:
toggleEpisodesLoaded
toggleShowInfoVisible
These actions don't receive any parameters as they toggle the boolean back and forth.
If the property's initial value is an array, you will get an action in the following format: concat<Reducer><Property>
So in our example, you will get:
concatEpisodesData
These actions accept an array as a parameter and will concat it to the state.
If the property's initial value is an object, you will get an action in the following format: extend<Reducer><Property>
So in our example, you will get:
extendEpisodesExpanded
These actions accept an object as a parameter and will extend the state with it.
In addition to the actions mentioned above, each property will also generate an action for setting and nullifying the property's value in case you need to overwrite things. They will be in the following format: set<Reducer><Property>
and nullify<Reducer><Property>
So in our example, you will get:
setEpisodesLoaded
nullifyEpisodesLoaded
setEpisodesData
nullifyEpisodesData
setEpisodesExpanded
nullifyEpisodesExpanded
setShowInfoVisible
nullifyShowInfoVisible
The set actions receive one parameter - the value which you want to set for that property. The nullify actions don't receive any value.
As a last resort, you can always override the entire value of a reducer using the override action. The override action is generated in the following format: override<Reducer>
.
The action takes an object as the parameter.
So in our example, you will get
overrideEpisodes
In your app you can use the action as you would with "regular" Redux action:
// Map directly if you are using thunk
import { actions } from '../redux/reducers';
const { extendEpisodesExpanded } = actions;
...
export default connect(mapStateToProps, { extendEpisodesExpanded })(EpisodesContainer);
// OR using bindActionCreators
import { bindActionCreators } from 'redux'
const mapDispatchToProps = (dispatch) => ({
extendEpisodesExpanded: bindActionCreators(extendEpisodesExpanded, dispatch)
});
export default connect(mapStateToProps, mapDispatchToProps)(EpisodesContainer);
// OR us the dispatch as a prop in your component
onClick() {
this.props.dispatch(extendEpisodesExpanded({ something: true }));
}
If you need to do something like fetching data from the server, you can fetch the data and then call one of the actions which were generated. Here is an example of using Redux Thunk to fetch a show and then it's episodes and finally dispatch the concatEpisodesData
action with the data when everything is complete:
// container
import { fetchEpisodes } from '../actions/episodeFetcher';
export default connect(mapStateToProps, { fetchEpisodes })(EpisodesContainer);
// episodesFetcher
import * as imdb from 'imdb-api';
import { actions } from '../redux/reducers';
const showId = 'tt4574334'; // the "Stranger Things" show id on imdb
const apiKey = 'your_omdb_token'; // an OMDB token
export const fetchEpisodes = () => (dispatch) => {
imdb.getById(showId, { apiKey }).then(results => {
results.episodes().then(episodes => {
dispatch(actions.concatEpisodesData(episodes));
})
});
}
None needed! The super reducer takes care of everything for you! 😎
I've create a repo and demo app with a fully working example which fetches the "Stranger Things" episode list from imdb. You can also toggle between the episode list or show's info and you can expand/collapse each episode to see more information. The example covers the toggle, concat, extend, set and nullify actions.
Repo: https://github.com/eyaleizenberg/imdb_list
Demo: https://eyaleizenberg.github.io/imdb_list/index.html
In the episodesActions.js
file, add your OMDB token. You can generate one here: https://www.omdbapi.com/apikey.aspx
To run:
cd example
npm install
npm start
Found a bug? Have a suggestion? Open an issue on Github. Like it? Show some love on Twitter @EyalEizenberg and @WixEng ❤️