Store is created using a configureStore snippet, which is
import {createStore, applyMiddleware, compose} from 'redux';
import rootReducer from '../reducers';
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(reduxImmutableStateInvariant()),
window.devToolsExtension ? window.devToolsExtension() : f => f
)
);
The rootReducer
used while composing the Store, is defined as a combination of all reducers in the app:
Every reducer gets the chance to define a new state, listening for actions and filtering them by their type, creating new layers of the global state object.
import {combineReducers} from 'redux';
import appState from './appStateReducer';
const rootReducer = combineReducers({
appState
});
Simply takes every reducer available and combine them into a single giant reducer capable of doing great things.
i.e. appStateReducer
export default ((state = [], action) => {
switch( action.type ){
case "PAGE_LOADING":
return Object.assign({}, state, { pageLoading: action.loading });
default:
return state;
}
})
Using the immutability of Object.assign(), creates a new state and replaces it in the Store.
i.e. appActions.js
export function setPageLoading(){
return { type: 'PAGE_LOADING', loading: true };
}
export function setPageLoaded(){
return { type: 'PAGE_LOADING', loading: false };
}
export function setState(state){
return { type: 'PAGE_LOADING', loading: state };
}
import React from 'react';
import Style from '../../styles/pageLoadingBar.scss';
import {connect} from 'react-redux';
import * as appActions from '../../actions/appActions';
class PageLoadingBar extends React.Component{
constructor(props, context){
super(props, context);
this.state = { visible: false };
}
render(){
return (
<div
className="pageLoadingBar"
style={{opacity: this.props.appState.pageLoading ? 1 : 0}}
>
<div className="loadingBar">
</div>
</div>
);
}
}
function mapStateToProps(state, ownProps){
return {
appState: state.appState
};
}
function mapDispatchToProps(dispatch){
return {
setLoading: () => dispatch(appActions.setPageLoading()),
setLoaded: () => dispatch(appActions.setPageLoaded()),
setState: loading => dispatch(appActions.setState(loading))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PageLoadingBar);
The decorations happens in the last line, using the connect function imported from 'react-redux'.
Uses a mapStateToProps
function that has to have two parameters: the global state of the app, and the props of the component.
The global state can be used to access the keys of the global state stored into the Store and to be used into the props of the decorated component using a custom key.
In this example appState: state.appState
means that accessing this.props.appState
from the component, you can have access to (global) state.appState
defined in the Store and named after the rootReducer convention.
The mapDispatchToProps
maps every key of the returning array into the props of the component. In the example if a this.props.setLoading
is called in the component, the action will be automatically dispatched.
If no mapDispatchToProps is defined, a dispatch
prop will be attached to the component, to be able to dispatch an action manually when firing an action.
An alternative way to bind actions to the dispatcher is using bindActionCreators
.
e.g.
import {bindActionCreators} from 'redux';
function mapDispatchToProps(dispatch){
return {
actions: bindActionCreators(appActions, dispatch)
}
}
This will let you use this.props.actions.<actionName>
in the component in order to access to the desired action.