Adding redux in React project (Refactor a simple project with Redux)
Consider a simple project, a counter application that works with two buttons, once for Increment and another for Decrement counter value.
In an actual scenario, we use the state for holding counter value like this:
in App.js:
import React, {Component} from 'react';
import CounterApp from './CounterApp'
class App extends Component {
render() {
return (
<CounterApp/>
);
}
}
export default App;
in CounterApp.js:
import React, {Component} from 'react';
class CounterApp extends Component {
state = {
counter: 0
};
handleIncrement = () => {
this.setState(prevState => ({
counter: prevState.counter + 1
}))
};
handleDecrement = () => {
this.setState(prevState => ({
counter: prevState.counter - 1
}))
};
render() {
return (
<div>
<button onClick={this.handleIncrement}>Increment</button>
<p>{this.state.counter}</p>
<button onClick={this.handleDecrement}>Decrement</button>
</div>
);
}
}
export default CounterApp;
A simple and basic example that implement with react class component and handled by two function handler (handleIncrement
and handleDecrement
)
And a state
with a value, counter
I'm using prevState
because of it's a best practice when you forced to use this.state.
in setState
!
Now, what would be this implementation with Redux?
First of all, you need to install redux and react-redux packages to your project via npm
or yarn
.
You can simply install them with one line of code:
npm install redux react-redux --save
or with yarn:
yarn add redux react-redux
now back to project and create 3 files with these names:
action.js
, reducer.js
and store.js
open action.js
file. We should implement two actions for this app. One for increment and one for decrement.
in action.js
const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
const increment = () => ({type: INCREMENT_COUNTER});
const decrement = () => ({type: DECREMENT_COUNTER});
export {
INCREMENT_COUNTER,
DECREMENT_COUNTER,
increment,
decrement
}
actions are simple functions that dispatched from component to redux
for changing the store
(state) via reducers.
so we should change reducer.js:
import {INCREMENT_COUNTER, DECREMENT_COUNTER} from "./action";
const initialState = {
counter: 0
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case(INCREMENT_COUNTER):
return {
...state,
counter: state.counter + 1
};
case (DECREMENT_COUNTER):
return {
...state,
counter: state.counter - 1
};
default:
return state
}
};
export default reducer
There are 3 main principles of using redux:
1- Single source of truth. The state of your whole application is
stored in an object tree within a single store.
2- The state is read-only. The only way to change the state is to emit
an action, an object describing what happened.
3- Changes are made with pure functions.
according to second principles, we must assume that the state is immutable, and each case(in switch) must return state individually.
using ...state in the returned state means that if initialState will changing in future, these cases will work properly (in this example it's not necessary).
our functions in actions are pure(3rd principle)
and for last new file store.js:
import {createStore} from "redux";
import reducer from './reducer'
const store = createStore(reducer);
export default store;
now we should apply this store to our App component.
so open App.js file and made these changes:
in App.js:
import React, {Component} from 'react';
import CounterApp from './CounterApp'
import {Provider} from 'react-redux'
import store from './store'
class App extends Component {
render() {
return (
<Provider store={store}>
<CounterApp/>
</Provider>
);
}
}
export default App;
Provider wrapped the CounterApp
component and will propagate store
to App
and CounterApp
and all other child components.
finally, change the CounterApp.js:
import React, {Component} from 'react';
import {connect} from "react-redux";
import {increment, decrement} from "./action";
class CounterApp extends Component {
handleIncrement = () => this.props.dispatch(increment());
handleDecrement = () => this.props.dispatch(decrement());
render() {
return (
<div>
<button onClick={this.handleIncrement}>Increment</button>
<p>{this.props.counter}</p>
<button onClick={this.handleDecrement}>Decrement</button>
</div>
);
}
}
const mapStateToProps = state => {
const counter = state.counter;
return {counter}
};
export default connect(mapStateToProps)(CounterApp);
we are using increment
& decrement
actions to dispatch actions to redux.
the state
was removed and instead of state we create a special function mapStateToProps' and use
connect` to connect the state to component props.
That's done!