Let's start with a UserList
example in Redux. First, create a directory with the application. We are using the Node.js server and npm package for this example because the Redux module is not available independently.
First, we have to download and install Node.js, if we have not already installed it in the system. We can download Node.js from http://nodejs.org . It includes the npm package manager.
Once the setup is done, we can check whether Node.js was set up properly or not. Open the command prompt window and run the following command:
node --version
You should be able to see the version information, which ensures that the installation was successful.
First we need to create a package.json
file for our project which includes the project information and dependencies. Now, open the command prompt/console and navigate to the directory you have created. Run the following command:
Npm init
This command will initialize our app and ask several questions to create a JSON file named package.json
. The utility will ask questions about the project name, description, entry point, version, author name, dependencies, license information, and so on. Once the command is executed, it will generate a package.json
file in the root directory of your project:
{ "name": "react-redux add ticket form example", "version": "1.0.0", "description": "", "scripts": { "start": "node server.js", "lint": "eslintsrc" }, "keywords": [ "react", "redux", "redux form", "reactjs", "hot", "reload", "live", "webpack" ], "author": "Harmeet Singh <[email protected]>", "license": "MiIT", "devDependencies": { "babel-core": "^5.8.3", "babel-eslint": "^4.0.5", "babel-loader": "^5.3.2", "css-loader": "^0.15.6", "cssnext-loader": "^1.0.1", "eslint": "^0.24.1", "eslint-plugin-react": "^3.1.0", "extract-text-webpack-plugin": "^0.8.2", "html-webpack-plugin": "^1.6.1", "react-hot-loader": "^1.2.7", "redux-devtools": "^1.0.2", "style-loader": "^0.12.3", "webpack": "^1.9.6", "webpack-dev-server": "^1.8.2" }, "dependencies": { "classnames": "^2.1.3", "lodash": "^3.10.1", "react": "^0.13.0", "react-redux": "^0.2.2", "redux": "^1.0.0-rc" } }
OK, let me explain to you some of the major tools before we start:
webpack-dev-server
: This is a server for live reload of our application.babel-loader
: This is the compiler for our JavaScript.redux-devtools
: This is a powerful tool for Redux development. Using this tool in development will help us to monitor the updates in the DOM UI.classnames
: This is amodule that will help us to apply the classes on condition.eslint
: This is a tool similar to JSHint and JSLint for parsing the JavaScript.First, we need to create webpack.config.js
and add the following code to enable the redux-devtools
:
var path = require('path'); varwebpack = require('webpack'); varExtractTextPlugin = require('extract-text-webpack-plugin'); vardevFlagPlugin = new webpack.DefinePlugin({ __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'true')) }); module.exports = { devtool: 'eval', entry: [ 'webpack-dev-server/client?http://localhost:3000', 'webpack/hot/only-dev-server', './src/index' ], output: { path: path.join(__dirname, 'dist'), filename: 'bundle.js', publicPath: '/static/' }, plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), devFlagPlugin, new ExtractTextPlugin('app.css') ], module: { loaders: [ { test: /.jsx?$/, loaders: ['react-hot', 'babel'], include: path.join(__dirname, 'src') }, { test: /.css$/, loader: ExtractTextPlugin.extract ('css-loader?module!cssnext-loader') } ] }, resolve: { extensions: ['', '.js', '.json'] } };
Now, create a directory with the name of src
. Inside this we need to create some folders, as shown in the following screenshot:
In every Redux application, we have actions, reducers, stores, and components. Let's start with creating some actions for our application.
Actions are the part of the information that sends data from our application to our store.
First we need to create the UsersActions.js
file inside the actions folder and put the following code inside it:
import * as types from '../constants/ActionTypes'; export function addUser(name) { return { type: types.ADD_USER, name }; } export function deleteUser(id) { return { type: types.DELETE_USER, id }; }
In the preceding code, we created two actions: addUser
and deleteUser
. Now we need to create ActionTypes.js
inside the constants
folder that defines the type
:
export constADD_USER = 'ADD_USER'; export constDELETE_USER = 'DELETE_USER';
Reducers handle the actions which describe the fact that something happened, but managing the state of the application is the responsibility of the reducers. They store the previous state
and action
and return
the next state
:
export default function users(state = initialState, action) { switch (action.type) { case types.ADD_USER: constnewId = state.users[state.users.length-1] + 1; return { ...state, users: state.users.concat(newId), usersById: { ...state.usersById, [newId]: { id: newId, name: action.name } }, } case types.DELETE_USER: return { ...state, users: state.users.filter(id => id !== action.id), usersById: omit(state.usersById, action.id) } default: return state; } }
We have defined the actions and reducers that represent the facts about what happened and when we need to update the state according to those actions.
The store
is the object that combines the actions and reducers. The store has the following responsibilities:
getState()
and dispatch
(action)subscribe
(listener)Here is the code of UserListApp.js
inside the container folder:
constinitialState = { users: [1, 2, 3], usersById: { 1: { id: 1, name: 'Harmeet Singh' }, 2: { id: 2, name: 'Mehul Bhatt' }, 3: { id: 3, name: 'NayanJyotiTalukdar' } } }; import React, { Component, PropTypes } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import * as UsersActions from '../actions/UsersActions'; import { UserList, AddUserInput } from '../components'; @connect(state => ({ userlist: state.userlist })) export default class UserListApp extends Component { static propTypes = { usersById: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired } render () { const { userlist: { usersById }, dispatch } = this.props; const actions = bindActionCreators(UsersActions, dispatch); return ( <div> <h1>UserList</h1> <AddUserInputaddUser={actions.addUser} /> <UserList users={usersById} actions={actions} /> </div> ); } }
In the preceding code, we are initializing the state of the component with the static JSON data of UserList
and using the getstate
, dispatch
(action), and we will update the store information.
These are the normal React JSX components, so we don't need to go into detail about them. We have added some functional stateless components that we'll use unless we need to use local state or the life cycle methods:
In this (AddUserInput.js
) file, we are creating a JSX input component from where we take the user input:
export default class AddUserInput extends Component { static propTypes = { addUser: PropTypes.func.isRequired } render () { return ( <input type="text" autoFocus="true" className={classnames('form-control')} placeholder="Type the name of the user to add" value={this.state.name} onChange={this.handleChange.bind(this)} onKeyDown={this.handleSubmit.bind(this)} /> ); } constructor (props, context) { super(props, context); this.state = { name: this.props.name || '', }; } }
In UserList.js
we are creating a list component where we iterate the value of the Input
component:
export default class UserList extends Component { static propTypes = { users: PropTypes.object.isRequired, actions: PropTypes.object.isRequired } render () { return ( <div className="media"> { mapValues(this.props.users, (users) => { return (<UsersListItem key={users.id} id={users.id} name={users.name} src={users.src} {...this.props.actions} />); }) } </div> ); } }
After iterating the value in the UserList
component, we are displaying that list in the Bootstrap media
layout:
export default class UserListItem extends Component { static propTypes = { id: PropTypes.number.isRequired, name: PropTypes.string.isRequired, onTrashClick: PropTypes.func.isRequired } render () { return ( <div> <div className="clearfix"> <a href="#" className="pull-left"> <img className="media-object img-thumbnail" src={"http://placehold.it/64x64"}/> </a> <div className={`media-body ${styles.paddng10}`}> <h3className="media-heading"> <strong><a href="#">{this.props.name}</a></strong> </h3> <p> Loremipsum dolor sit amet, consecteturadipiscingelit. Praesentgravidaeuismod ligula, vel semper nuncblandit sit amet. </p> <div className={`pull-right ${styles.userActions}`}> <button className={`btnbtn-default ${styles.btnAction}`} onClick={()=>this.props.deleteUser(this.props.id)} > Delete the user <iclassName="fafa-trash" /> </button> </div> </div> </div> </div> ); } }
Now we need to wrap our components in UserListApp.js
inside the container folder:
import { UserList, AddUserInput } from '../components'; @connect(state => ({ userlist: state.userlist })) export default class UserListApp extends Component { static propTypes = { usersById: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired } render () { const { userlist: { usersById }, dispatch } = this.props; const actions = bindActionCreators(UsersActions, dispatch); return ( <div> <h1>UserList</h1> <AddUserInput addUser={actions.addUser} /> <UserList users={usersById} actions={actions} /> </div> ); } }
Now, let's wrap this UserListApp
component to the Redux store in App.js
inside the container folder:
import UserListApp from './UserListApp'; import * as reducers from '../reducers'; const reducer = combineReducers(reducers); const store = createStore(reducer); export default class App extends Component { render() { return ( <div> <Provider store={store}> {() => <UserListApp /> } </Provider> {renderDevTools(store)} </div> ); } }
Now go to the root directory, open the CMD, and run the following command:
To install the packages that we need for this app, run the following command:
Npm install
Once it's complete, run the following command:
Npm start
Observe the following screenshot:
That looks amazing. In the right-side panel is the Redux DevTool which gives the update of the UI. We can easily see the updates for deleting or adding the user in this list.
The following screenshot shows the deletion of a user from UserList
:
The following screenshot shows the addition of a user:
Please see the source code for Chapter 6, Redux Architecture to get a proper understanding about the flow of the application.