ref – https://eslint.org/docs/rules/object-shorthand
Say we want to add Reporting component to the web app.
We first create the component.
We do a simple return of basic html.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import React from 'react'; class Reporting extends React.PureComponent { componentDidMount = () => { } render() { return ( <div><b>Testing 1 2 3</b></div> ) } } export default Reporting; |
Now that this component exists, we import it into our App file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//actions/components/App.js import Reporting from '../containers/Reporting' const App = function() { return ( <div> ... <Reporting /> </div> ) } export default App |
You should now see “Testing 1 2 3” in the output.
Adding the reducer
We now add the reducer. The reducer controls the state the data is in. Thus, naturally, we want to have an initial state. We want to say that whatever happens in the app, according to MACROS that gets passed in, we want to return the state of the data.
Thus, we declare the MACROS in the action file. The action file does two things:
1) contain functions that return action objects. Notably, there is a type property in this action object that contains a MACRO. This MACRO describes what kind of action it is.
i.e fetching report has started, fetching report error, fetching report finished.
2) contains the MACRO definition themselves
/src/actions/index.js
1 2 3 |
export const FETCH_REPORTING_STARTED = 'FETCH_REPORTING_STARTED'; export const FETCH_REPORTING_ERRORED = 'FETCH_REPORTING_ERRORED'; export const FETCH_REPORTING_FINISHED = 'FETCH_REPORTING_FINISHED'; |
and then say if the action type is
this, then we want to return a certain state.
In our case, we have a state where the fetching of the report has just started.
So we want to set the property to certain defaults.
However, notice that the reducer 1st parameter is the initial state. So make sure you set that.
/src/reducers/reportingReducer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { FETCH_REPORTING_STARTED, } from '../actions'; const reportingReducer = (state = { reportingData: null, reportingLoading: false, reportingError: null, }, action) => { switch (action.type) { case FETCH_REPORTING_STARTED: return state; default: return state; } }; export default reportingReducer; |
Add in mapStateToProps and connect
Now we import connect function from redux and use it to connect our component to the store.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import React from 'react'; import { connect } from 'react-redux' class Reporting extends React.PureComponent { componentDidMount = () => { console.log('src/containers/Reporting.js - componentDidMount'); } render() { console.log('src/containers/Reporting.js - render') console.log(this.props); return ( <div><b>Testing 1 2 3 4 5</b></div> ) } } ... ... console.log('√ connect data to Reporting component') connect(mapStateToProps)(Reporting); export default Reporting; |
Right now, we just simply connected the component to our mapStateToProps.
Hence, when we refresh the web app our output goes in this order:
1) Component initializes with connect function
the connect function gets run first. – √ connect data to Reporting component.
2) Initialize all reducers
all reducers will receive a default action object with a system INIT macro string. You won’t have to worry about it. Your reducers should simply return the initial states. This will happen for all reducers like so:
reducers/todos.js
1 2 3 4 5 |
const todos = (state = [], action) => { ... default: return state; // returns this state } |
reducers/reportingReducer.js
1 2 3 4 5 6 7 8 9 |
const reportingReducer = (state = { reportingData: null, reportingLoading: false, reportingError: null, }, action) => { ... default: return state; // returns this state } |
reducers/visibilityFilter.js
1 2 3 4 |
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => { ... default: return state; // returns this state } |
3) combineReducers
Now that all the reducers have been initialized, the next step is that we use Object Literal Shorthand in order to create an object literal and declare its properties and values.
so for example this:
1 2 3 4 5 6 7 |
// where data x, y, z exists somewhere var foo = { x: x, y: y, z: z, }; |
becomes this:
1 2 3 4 5 6 7 8 |
/*eslint-env es6*/ // properties var foo = { x, y, z }; |
hence, we create an object literal with all the reducers as properties like so:
1 2 3 4 5 |
{ todos, visibilityFilter, reportingReducer, } |
then insert it into combineReducers:
src/reducers/index.js
1 2 3 4 5 6 7 8 9 10 11 12 |
import todos from './todos' import visibilityFilter from './visibilityFilter' import reportingReducer from './reportingReducer' // created a object literal with short hand notation // insert the object literal into combineReducers export default combineReducers({ todos, visibilityFilter, reportingReducer, }) |
In other words, we created a object literal using short hand notation.
We declare property ‘todos’, and point it to object todos.
We declare property ‘visibilityFilter’ and point it to object visibilityFilter.
We declare property ‘reportingReducer’ and point it to object reportingReducer.
4) createStore
We then pass this temporary object into the createStore parameter. As a result, we get a singleton store. What’s in the store will reflect what’s in this object.
1 2 3 4 |
import { createStore } from 'redux' import rootReducer from './reducers' const store = createStore(rootReducer) |
Now, in mapStateToProps of our Reporting component, let’s look at
1 2 3 4 |
const mapStateToProps = function(reportingState) { console.log(reportingState); ... } |
Now that the reducers were initialized, and the store created, the store state gets passed into our mapStateToProps.
It will be logged as:
{
reportingReducer: {reportingData: null, reportingLoading: false, reportingError: null}
todos: []
visibilityFilter: “SHOW_ALL”
__proto__: Object
}
The reason why it has those properties is because earlier we created an temporary object with data from our reducer files, and initialized the store with it.
5. mapStateToProps
Once we reach this function, we’re literally trying to map the store’s data into Reporting component’s this.props.
Thus, the reportingState should have all the data of the store. You can check by logging reportingState as shown. Before the other functions are called, this mapStateToProps will be executed. You can decide what kind of objects are returned. These returned objects will be reflected in your this.props.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const mapStateToProps = function(reportingState) { console.log('src/containers/Reporting.js - mapStateToProps'); console.log(reportingState); if (reportingState) { return reportingState; } else { console.log( 'mapStateToProps: state is null'); return { reportingReducer: { reportingData: null, reportingLoading: false, reportingError: null }, todos: [], visibilityFilter: VisibilityFilters.SHOW_ALL, }; } } |
Now when the component refreshes itself by calling render and componentDidLoad, check log your this.props. You’ll see that it has been updated with the store data.
1 2 3 4 5 6 7 8 |
componentDidMount = () => { console.log(this.props); } render() { console.log(this.props); return (<div><b>Testing 1 2 3 4 5</b></div>) } |
dispatch: ƒ dispatch(action)
reportingReducer: {reportingData: null, reportingLoading: false, reportingError: null}
todos: []
visibilityFilter: “SHOW_ALL”
__proto__: Object
Creating a trigger
In your reporting.js, insert this code. We have inserted an object to represent mapDispatchToProps.
1 2 3 |
export default connect(mapStateToProps, { fetchReportingAction: fetchReporting, })(Reporting); |
Let’s use this dispatch function in our componentDidMount:
1 2 3 4 5 6 7 8 |
componentDidMount = () => { console.log('src/containers/Reporting.js - componentDidMount'); console.log(this.props); const { fetchReportingAction } = this.props; console.log('-------------------> calling fetchReportingAction'); fetchReportingAction(); } |
We then execute the action of fetching the reports.
output:
——————-> calling fetchReportingAction
index.js:67 actions/index.js – called fetchReporting
An action will trigger all the reducers via the connect function.
Hence, all of our reducers will check to see they handle the action.type.
todos.js:3 — reducers/todos.js —
todos.js:4 received action:
todos.js:5 Object
todos.js:26 todos.js – no action.type found. default return state
visibilityFilter.js:5 — reducers/visibilityFilter.js —
visibilityFilter.js:11 visibliytFilter.js – no action.type found. default return state
Going through todos and visibilityFilter reducers, we do not handle the specific action.type.
However, in reporting reducer, the action.type matches.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const reportingReducer = (state = { reportingData: null, reportingLoading: false, reportingError: null, }, action) => { ... ... switch (action.type) { case FETCH_REPORTING_STARTED: return {...} default: return state; } } |
Hence, say we want to update reportingData property with: [‘ricky’, ‘joy’, ‘en en’]
We update it by returning a temp object with the new values.
1 2 3 4 5 6 7 |
case FETCH_REPORTING_STARTED: return { ...state, reportingData: ['ricky', 'joy', 'en en'], reportingLoading: true, reportingError: null, }; |
output:
reportingReducer.js:10 — reducers/reportingReducer.js —
reportingReducer.js:12 received action is:
reportingReducer.js:13 Object
reportingReducer.js:18 — action.type found! –FETCH_REPORTING_STARTED
reportingReducer.js:19 returning state object with updated action.data…
Once the store gets updated, mapStateToProps of our Reporting component is called to let us know we can update our component with whatever data we want from the whole store.
Reporting.js
1 2 3 |
const mapStateToProps = function(storeState) { ... } |
Take note that parameter storeState has data from the whole store. It looks like this:
reportingReducer: {reportingData: Array(3), reportingLoading: true, reportingError: null}
todos: []
visibilityFilter: “SHOW_ALL”
We need to filter what we want. For example, if we only need reportingReducer for our this.props, then we need to return just that part.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const mapStateToProps = function(storeState) { if (storeState) { return storeState.reportingReducer; // we only care about data from reporting reducer } else { console.log( 'mapStateToProps: state is null'); return { data: null, loading: false, error: null, }; } } |
Then the this.props in your render and other component functions would only have data like this:
{reportingData: Array(3), reportingLoading: true, reportingError: null, fetchReportingAction: ƒ}
fetchReportingAction: ƒ ()
reportingData: (3) [“ricky”, “joy”, “en en”]
reportingError: null
reportingLoading: true