[React] Redux

Redux work flow

Install npm install --save redux

Redux is like a central store which stores entire application state.

redux

Reducer:
Is a function, accepts two arguments: (prevState, action).

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// redux-basics.js

// node.js
const redux = require('redux');
const createStore = redux.createStore; // Setting up store

const initialState = {
counter: 0
};

// Reducer
const rootReducer = (state = initialState, action) => { // initialize current state
if (action.type == 'INC_COUNTER') {
return {
...state,
counter: state.counter + 1
}; // return a new updated javascript object (immutably)
}

if (action.type == 'ADD_COUNTER') {
return {
...state,
counter: state.counter + action.value;
}
}
return state;
}; // return updated state

// Store
const store = createStore(rootReducer);
console.log(store.getState());

// Subscription: will be executed whenever the dispatching action is and new state is stored
store.subscribe(() => {
console.log('[Subscription]', store.getState());
});


// Dispatching Action
store.dispatch({type: 'INC_COUNTER'});
store.dispatch({type: 'ADD_COUNTER', value: 10}); // increase value by 10
console.log(store.getState());

node redux-basics.js

In project

  • Install npm install --save react-redux

Provider: allows us to inject store into react component.

1
2
3
4
5
6
7
8
9
// Index.js

import { createStore } from 'redux';
import reducer from './store/reducer';
import { Provider } from 'react-redux'; // Help to inject redux into react

const store = createStore(reducer); // has a reducer as input

ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'))
  • Create a store folder under src directory
  • Create a file named reducer.js in store
1
2
3
4
5
6
7
8
9
const initialState = {
counter: 0
}

const reducer = (state = initialState, action) => {
return state;
}

export default reducer;

Subscribe to the dispatch

connect is a function that returns a function as higher order component. In the argument, it accepts:

- Which `state` do we want to change
- Which `action` do we want to dispatch
1
2
3
4
5
6
7
8
9
10
11
12
13
// In container component
import { connect } from 'react-redux';

class Counter extends Component {

}

const mapStateToProps = state => { // configure which data we need from redux to map into prpps of this component, stores a function
return {
Ctr: state.counter, // Prop Name: Redux global state
};
}
export default connect(mapStateToProps)(Counter);

Dispatch Actions to update global state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// In container component
class Counter extends Component {
render() {
return(
<CounterControl clicked={this.props.onIncreamentCounter}
);
}
}
const mapDispatchToProps = dispatch => {
return {
onIncrementCounter: () => dispatch({type: 'ADD', val: 10}),
onSubtractCounter: () => dispatch({type: 'SUBTRACT', val: 1}),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

In reducer.js

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
27
28
29
30
31
32
33
34
35
36
37
38
39
const reducer = (state = initialState, action) => {
if (action.type === 'INCREMENT') {
return {
counter: state.counter + action.val
}
}
return state;
}

// With switch syntax
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'TOWHITE':
return {
...state, // KEEP OTHER STATE UNCHANGED! AVOID RETURNING UNDEFINED
white: true,
};
case 'TOBLACK':
return {
...state,
white: false,
}
case 'OPENOVERLAY':
return {
...state,
overlayOpen: true,
}
case 'CLOSEOVERLAY':
return {
...state,
overlayOpen: false,
}
default: {
return {
...state
}
}
}
}

Oursourcing Actions

1
2
3
4
 // actions.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const ADD = 'ADD';
1
2
3
4
5
6
7
8
9
// reducer.js
import * as actionTypes from './actions';

const reducer = ( state = initialState, action ) => {
switch ( action.type ) {
case actionTypes.INCREMENT:
return ...
}
}
1
2
3
4
5
6
7
8
// component.js
import * as actionTypes from '../store/actions';
...
const mapDispatchToProps = dispatch => {
return {
onIncrementCounter: () => dispatch({ type: actionTypes.INCREMENT })
}
}

Add multiple reducers