React Redux Todo List
A simple todo list application using react and redux
If you understand the basic concept in React and Redux. All you need to focus are Action, Reducers, Store, and Components. In this project is about creating a React and Redux Todo List app creating your own server path and using webpack hot reload to make it real time updated!
For the file structure, action, reducer, and store will be kept in folder name redux. In the same way as before, all component like App (entry point), TodoInput (input text), TodoItems (a todo), and TodoList (all todos) in components, client, server, and config file.
react-todo-list
|– client
|–– client.js
|–– index.html
|– components
|–– App.js
|–– TodoInput.js
|–– TodoItems.js
|–– TodoList.js
|– redux
|–– actions.js
|–– reducer.js
|–– store.js
|- server
|–– server.js
|– package.json
|– webpack.config.js
In package.json will keep all project information about the dependencies, description. as well as the script.
Start from the scratch, we create a new folder name react-todo-list, go into the folder then initialize with npm.
$ mkdir react-todo-list
$ cd react-todo-list
$ npm init
Accept the default, maybe add some description or author name.
To install the webpack and dependencies to package.json
$ npm install webpack --save
Ps.
i = install
-S =--save
$ npm i -S react react-dom react-redux
$ npm i -S redux redux-logger redux-storage
$ npm i -S babel-loader
$ npm i -S webpack-hot-middleware
$ npm i -S babel-loader babel-preset-es2015 babel-preset-react
$ npm i -S express webpack-dev-middleware
Name “serve” in this example is refers to the server files using with nodemon — reload, automatically.
{ "name": "react-todo-list", "version": "1.0.0", "description": "Todo List App", "license": "MIT", "author": "____YOUR_NAME____", "scripts": { "serve": "nodemon server/server.js --ignore components" },}
webpack.config.js
var webpack = require('webpack'); module.exports = { devtool: 'inline-source-map', entry: [ 'webpack-hot-middleware/client', './client/client.js' ], output: { path: require("path").resolve("./dist"), filename: 'bundle.js', publicPath: '/' }, plugins: [ new webpack.optimize.OccurrenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin() ], module: { loaders: [ { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { presets: ['react', 'es2015'] } } ] }}
Then first thing is to create a reducer with a default that returns the state.
redux/reducer.js
let reducer = (state, action) => { switch (action.type) { default: return state; }}export default reducer
Following by a store that collects the todo like the following code.
redux/store.js
import { applyMiddleware, compose, createStore } from 'redux'
import reducer from './reducer'
import logger from 'redux-logger'let finalCreateStore = compose( applyMiddleware(logger()))(createStore)export default function configureStore(initialState = { todos: [] }) { return finalCreateStore(reducer, initialState)}
redux/actions.js
let actions = {}export default actions
Next, creating an HTML web page for todo list application, then create an entry point that connects React with Redux.
client/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Todo List</title>
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
To this point create an entry point that connects React with Redux.
client/client.js
import React from 'react'
import { render } from 'react-dom'
import App from '../components/App'
import configureStore from '../redux/store'
import { Provider } from 'react-redux'let initialState = {
todos: []
}let store = configureStore(initialState)render( <Provider store={store}> <App /> </Provider>,document.getElementById('app'))
Then create a server config file that call HTML page on the setup port.
server/server.js
var express = require('express');
var path = require('path');
var config = require('../webpack.config.js');
var webpack = require('webpack');
var webpackDevMiddleware = require('webpack-dev-middleware');
var webpackHotMiddleware = require('webpack-hot-middleware');var app = express();
var compiler = webpack(config);app.use(webpackDevMiddleware(compiler, {noInfo: true, publicPath: config.output.publicPath}));app.use(webpackHotMiddleware(compiler));app.use(express.static('./dist'));app.use('/', function (req, res) { res.sendFile(path.resolve('client/index.html'));});# ---- You can set up port right here ----
var port = 8000;app.listen(port, function(error) { if (error) throw error; console.log("Express server listening on port", port);});
Build up all components by binding the data.
components/TodoInput.js
import React, { Component } from 'react'class TodoInput extends Component { constructor(props) { super(props) this.state = { title: '' } } handleTitleChange(event) { console.log(event); this.setState({ title: event.target.value }) } handleSubmit(event) { event.preventDefault() if (this.state.title != '') { this.props.addTodo(this.state.title) //reset input box this.setState({ title: '' }) } } render() { return ( <div className="todo__input">
<div className="title">
<input
type="text"
placeholder="title.."
required={true}
value={this.state.title}
onChange={this.handleTitleChange.bind(this)}
/>
</div>
<div className="submitButton">
<button onClick={this.handleSubmit.bind(this)}>Add</button>
</div>
</div> ) }}export default TodoInput
components/TodoItem.js
import React, { Component } from 'react'class TodoItem extends Component { constructor(props) { super(props) this.state = { title: this.props.todo.title, completed: this.props.todo.completed, } } handleComplete() { this.props.actions.completeTodo(this.props.todo.id) this.setState({ checked: !this.state.checked }) } handleDelete() { this.props.actions.deleteTodo(this.props.todo.id) } handleEdit() { this.props.actions.editTodo(this.props.todo.id) } handleUpdate() { if (this.state.title != '') { this.props.actions.updateTodo(this.props.todo.id,
this.state.title) } } handleTitleChange(event) { this.setState({ title: event.target.value }) } handleDescriptionChange(event) { this.setState({ description: event.target.value }) } handleDueDateChange(event) { console.log(event); this.setState({ dueDate: event }) } handleFocusChange(event) { console.log(event); this.setState({ focused: event.focused }) } render() { return ( <li className="todo__item">
<input type="checkbox"
defaultChecked={this.state.completed}
required={true}
onClick={this.handleComplete.bind(this)}/>
<input type="text"
onChange={this.handleTitleChange.bind(this)}
required={true}
placeholder='title'
value={this.state.title}/>
<button onClick=
{this.handleDelete.bind(this)}>Delete</button>
</li> ) }}export default TodoItem
components/TodoList.js
import React, { Component } from 'react'
import TodoItem from './TodoItem'class TodoList extends Component { render() { return ( <ul className="todo__list"> { this.props.todos.map((todo) => { if (todo.display === true) { return <TodoItem key={todo.id} todo={todo} actions=
{this.props.actions}/> } }) } </ul> ) }}export default TodoList
components/App.js
import React, { Component } from 'react'
import TodoInput from './TodoInput'
import TodoList from './TodoList'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actions from '../redux/actions'class App extends Component { render() { return (
<div>
<h1 className="header">Todos</h1>
<TodoInput addTodo={this.props.actions.addTodo}/>
<TodoList actions={this.props.actions} todos= {this.props.todos}/>
</div> ) }}function mapStateToProps(state) { return state}function mapDispatchToProps(dispatch) { return { actions: bindActionCreators(actions, dispatch) }}export default connect(mapStateToProps, mapDispatchToProps)(App)
Let’s create some action to Todo List app in your action.js and reducer.js files
redux/reducer.js
function getId(state) { return state.todos.reduce((maxId, todo) => { return Math.max(todo.id, maxId) }, -1) + 1}let reducer = (state, action) => { switch (action.type) { case 'ADD_TODO': return Object.assign({}, state, { todos: [{ title: action.title, completed: false, id: getId(state) }, ...state.todos] }) case 'COMPLETE_TODO': return Object.assign({}, state, { todos: state.todos.map((todo) => { return todo.id === action.id ? Object.assign({}, todo, {updated: false, completed: !
todo.completed}) : todo }) }) case 'DELETE_TODO': return Object.assign({}, state, { todos: state.todos.filter((todo) => { return todo.id !== action.id }) }) default: return state; }}export default reducer
redux/actions.js
let actions = { addTodo: function(title) { return { type: 'ADD_TODO', title: title, } }, completeTodo: function(id) { return { type: 'COMPLETE_TODO', id: id } }, deleteTodo: function(id) { return { type: 'DELETE_TODO', id: id } }}export default actions
Run the project using the following command
$ npm run serve
And … There you go!
Hope this will help you in creating your own react and redux project. :)