Overview
Now a days hottest libraries in front-end development is Redux. Lots of developers are confused about what it is and what its benefits are.
Redux is a traditional library or a framework like AngularJS. Redux converts state of application in a single immutable state tree which is not directly changed. Dan Abramov create a state management it is Redux. It can do hot reloading, implement logging, time travel,record and replay.
Install Redux
First, you have to create react simple app with create-react-app then you have to install redux :
npm install --save redux
React with Connect to Redux
You want to link react site to the redux, let your web know that store exists.
Redux is given by the “react-redux” library, the first major part of this library, which is the Provider.
npm install --save react-redux
Provider
Provider serves one purpose to “provide” the store to its child components. It create stores access to it’s children and you want your whole app to access the store so you can put your App component with in provider. Provider is “wraps” the entire application tree. Only components within provider can be connected.
import { createStore } from 'redux'; import { Provider } from 'react-redux' import App from './App'; const store = createStore(); ReactDOM.render(<Provider store={store}> <App /></Provider>,document.getElementById('root'));
Store
The store is one big JavaScript object that has tons of key-value pairs that represent the current state of the application. Unlike the state object in React that is sprinkled across different components, you have only one store. The store provides the application state, and every time the state updates, the view rerenders.
However, you can never mutate or change the store. Instead, you create new versions of the store.
(previousState, action) => newState
Because of this, you can do time travel through all the states from the time the app was booted on your browser.
The store has three methods to communicate with the rest of the architecture. They are:
- Store.getState()—To access the current state tree of your application.
- Store.dispatch(action)—To trigger a state change based on an action. More about actions below.
- Store.subscribe(listener)—To listen to any change in the state. It will be called every time an action is dispatched.
Action/Dispatch Creators
Actions are also plain JavaScript objects that send information from your application to the store. If you have a very simple counter with an increment button, decrement button, add button and subtract button pressing increment button will result in an action being triggered that looks like this:
{ type: "INCREMENT", value: 1 }
They are the only source of information to the store. The state of the store changes only in response to an action. Each action should have a type property that describes what the action object intends to do. Other than that, the structure of the action is completely up to you. However, keep your action small because an action represents the minimum amount of information required to transform the application state.
For instance, in the example above, the type property is set to “INCREMENT”,”DECREMENT” and an additional payload property is included. You could change the payload property to something meaningful. Now dispatch to the store like this.
store.dispatch({type: "INCREMENT"}); store.dispatch({type: "DECREMENT"});
Subscribe
You are going to watch to any changes in the store, and then log the current state of the store.
store.subscribe( () => { console.log("State has changed" + store.getState()); })
So how do you update the store? Redux has something called actions that make this happen.
Connect
You can connect your components to provider. You already established that there is no way to directly interact with the store. You can either retrieve data by obtaining its current state and change its state by dispatching an action.
This code uses connect to map the stores state and dispatch to the props of a component :
import { connect } from 'react-redux' class Counter extends Component { render() { return ( <div> <CounterOutput value={this.props.ctr} /> <CounterControl label="Increment" clicked={this.props.onIncrementCounter} /> <CounterControl label="Decrement" clicked={this.props.onDecrementCounter} /> <CounterControl label="Add 5" clicked={this.props.onAddCounter} /> <CounterControl label="Subtract 5" clicked={this.props.onSubtractCounter} /> </div> ); } } const mapStateToProps = state => { return { ctr: state.counter, } } const mapDispatchToProps = dispatch => { return { onIncrementCounter: () => { dispatch({ type: 'INCREMENT' }) }, onDecrementCounter: () => { dispatch({ type: 'DECREMENT' }) }, onAddCounter: () => { dispatch({ type: 'ADD', value: 5 }) }, onSubtractCounter: () => { dispatch({ type: 'SUBTRACT', value: 5 }) }, } } export default connect(mapStateToProps, mapDispatchToProps, null, { pure: false })(Counter);
Functions mapStateToProps are provide “state” and mapDispatchToProps “dispatch”to store and return an object, it’s keys will then be passed on as the props of the component they are connected to.
In this case, mapDispatchToProps returns an object with key : “onIncrementCounter”, ”onDecrementCounter”, ”onAddCounter”, ”onSubstractCounter” and mapStateToProps returns an object with the ctr key.
The connected component (which is exported) provides “onIncrementCounter”,”onDecrementCounter”,”onAddCounter” ,”onSubstractCounter” and ctr as props to Counter.
Third option in connect() is mergeProps and forth one is options, both are optional in connect().In the connect’s fourth options object there is a pure parameter it is Boolean. If it is true then connect() will avoid re-rendering.
When you want to retrieve data, you don’t get it directly from the store. Instead, you get a snapshot of the data in the store at any point in time using store.getState(). To obtain data you need to get the current state of the store.
Reducer
An action or dispatch describes the problem, and the reducer is responsible for solving the problem. In the earlier example, the incrementCount method returned an action that supplied information about the type of change that you wanted to make to the state. Reducer use this information to update the state. There is a point highlighted in the docs that you should always remember while using Redux.
Reducer should calculate the upcoming state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.
What this means is that a reducer should be a pure function. A set of inputs should return the same to same output. Beyond that, it shouldn’t do anything more. A reducer is not the place for side effects such as making AJAX calls.
Let’s fill in the reducer for your counter.
Reducer.js
const initialState = { counter: 0, results: [ ] } const reducer = (state = initialState, action) => { switch (action.type) { case 'INCREMENT': return { ...state, counter: state.counter + 1 } case 'DECREMENT': return { ...state, counter: state.counter - 1 } case 'ADD': return { ...state, counter: state.counter + action.value } case 'SUBTRACT': return { ...state, counter: state.counter - action.value } default: return { ...state, counter: state.counter } } return state; } export default reducer;
You don’t mutate the state. You create a copy with Object.assign(state, { counter: state.counter + action.value }) is also wrong: it will mutate the first argument. You must supply an empty object as the first argument. You can also enable the object spread operator proposal to write { …state, counter: state.counter + 1 } instead. You return the previous state in the default case. It’s important to return the previous state for any unknown action.
Summary
This docs was meant to be a starting point for managing state in React with Redux. I have covered all the things and understand the basic Redux concepts such as the provider, connect, store, dispatch / actions and reducers. Towards the end of the docs, I have also created a working redux demo counter. Although it wasn’t much, we learned how all the pieces of the puzzle fit together.