Example: Todo List

This is the complete source code of the tiny todo app we built during the basics tutorial. This code is also in our repository of examples and can be run in your browser via CodeSandbox.

Entry Point

index.js

  1. import React from 'react'
  2. import { render } from 'react-dom'
  3. import { Provider } from 'react-redux'
  4. import { createStore } from 'redux'
  5. import rootReducer from './reducers'
  6. import App from './components/App'
  7. const store = createStore(rootReducer)
  8. render(
  9. <Provider store={store}>
  10. <App />
  11. </Provider>,
  12. document.getElementById('root')
  13. )

Action Creators

actions/index.js

  1. let nextTodoId = 0
  2. export const addTodo = text => ({
  3. type: 'ADD_TODO',
  4. id: nextTodoId++,
  5. text
  6. })
  7. export const setVisibilityFilter = filter => ({
  8. type: 'SET_VISIBILITY_FILTER',
  9. filter
  10. })
  11. export const toggleTodo = id => ({
  12. type: 'TOGGLE_TODO',
  13. id
  14. })
  15. export const VisibilityFilters = {
  16. SHOW_ALL: 'SHOW_ALL',
  17. SHOW_COMPLETED: 'SHOW_COMPLETED',
  18. SHOW_ACTIVE: 'SHOW_ACTIVE'
  19. }

Reducers

reducers/todos.js

  1. const todos = (state = [], action) => {
  2. switch (action.type) {
  3. case 'ADD_TODO':
  4. return [
  5. ...state,
  6. {
  7. id: action.id,
  8. text: action.text,
  9. completed: false
  10. }
  11. ]
  12. case 'TOGGLE_TODO':
  13. return state.map(todo =>
  14. todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
  15. )
  16. default:
  17. return state
  18. }
  19. }
  20. export default todos

reducers/visibilityFilter.js

  1. import { VisibilityFilters } from '../actions'
  2. const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
  3. switch (action.type) {
  4. case 'SET_VISIBILITY_FILTER':
  5. return action.filter
  6. default:
  7. return state
  8. }
  9. }
  10. export default visibilityFilter

reducers/index.js

  1. import { combineReducers } from 'redux'
  2. import todos from './todos'
  3. import visibilityFilter from './visibilityFilter'
  4. export default combineReducers({
  5. todos,
  6. visibilityFilter
  7. })

Presentational Components

components/Todo.js

  1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. const Todo = ({ onClick, completed, text }) => (
  4. <li
  5. onClick={onClick}
  6. style={{
  7. textDecoration: completed ? 'line-through' : 'none'
  8. }}
  9. >
  10. {text}
  11. </li>
  12. )
  13. Todo.propTypes = {
  14. onClick: PropTypes.func.isRequired,
  15. completed: PropTypes.bool.isRequired,
  16. text: PropTypes.string.isRequired
  17. }
  18. export default Todo

components/TodoList.js

  1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. import Todo from './Todo'
  4. const TodoList = ({ todos, toggleTodo }) => (
  5. <ul>
  6. {todos.map(todo => (
  7. <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />
  8. ))}
  9. </ul>
  10. )
  11. TodoList.propTypes = {
  12. todos: PropTypes.arrayOf(
  13. PropTypes.shape({
  14. id: PropTypes.number.isRequired,
  15. completed: PropTypes.bool.isRequired,
  16. text: PropTypes.string.isRequired
  17. }).isRequired
  18. ).isRequired,
  19. toggleTodo: PropTypes.func.isRequired
  20. }
  21. export default TodoList
  1. import React from 'react'
  2. import PropTypes from 'prop-types'
  3. const Link = ({ active, children, onClick }) => (
  4. <button
  5. onClick={onClick}
  6. disabled={active}
  7. style={{
  8. marginLeft: '4px'
  9. }}
  10. >
  11. {children}
  12. </button>
  13. )
  14. Link.propTypes = {
  15. active: PropTypes.bool.isRequired,
  16. children: PropTypes.node.isRequired,
  17. onClick: PropTypes.func.isRequired
  18. }
  19. export default Link
  1. import React from 'react'
  2. import FilterLink from '../containers/FilterLink'
  3. import { VisibilityFilters } from '../actions'
  4. const Footer = () => (
  5. <div>
  6. <span>Show: </span>
  7. <FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
  8. <FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
  9. <FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>Completed</FilterLink>
  10. </div>
  11. )
  12. export default Footer

components/App.js

  1. import React from 'react'
  2. import Footer from './Footer'
  3. import AddTodo from '../containers/AddTodo'
  4. import VisibleTodoList from '../containers/VisibleTodoList'
  5. const App = () => (
  6. <div>
  7. <AddTodo />
  8. <VisibleTodoList />
  9. <Footer />
  10. </div>
  11. )
  12. export default App

Container Components

containers/VisibleTodoList.js

  1. import { connect } from 'react-redux'
  2. import { toggleTodo } from '../actions'
  3. import TodoList from '../components/TodoList'
  4. import { VisibilityFilters } from '../actions'
  5. const getVisibleTodos = (todos, filter) => {
  6. switch (filter) {
  7. case VisibilityFilters.SHOW_ALL:
  8. return todos
  9. case VisibilityFilters.SHOW_COMPLETED:
  10. return todos.filter(t => t.completed)
  11. case VisibilityFilters.SHOW_ACTIVE:
  12. return todos.filter(t => !t.completed)
  13. default:
  14. throw new Error('Unknown filter: ' + filter)
  15. }
  16. }
  17. const mapStateToProps = state => ({
  18. todos: getVisibleTodos(state.todos, state.visibilityFilter)
  19. })
  20. const mapDispatchToProps = dispatch => ({
  21. toggleTodo: id => dispatch(toggleTodo(id))
  22. })
  23. export default connect(
  24. mapStateToProps,
  25. mapDispatchToProps
  26. )(TodoList)
  1. import { connect } from 'react-redux'
  2. import { setVisibilityFilter } from '../actions'
  3. import Link from '../components/Link'
  4. const mapStateToProps = (state, ownProps) => ({
  5. active: ownProps.filter === state.visibilityFilter
  6. })
  7. const mapDispatchToProps = (dispatch, ownProps) => ({
  8. onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
  9. })
  10. export default connect(
  11. mapStateToProps,
  12. mapDispatchToProps
  13. )(Link)

Other Components

containers/AddTodo.js

  1. import React from 'react'
  2. import { connect } from 'react-redux'
  3. import { addTodo } from '../actions'
  4. const AddTodo = ({ dispatch }) => {
  5. let input
  6. return (
  7. <div>
  8. <form
  9. onSubmit={e => {
  10. e.preventDefault()
  11. if (!input.value.trim()) {
  12. return
  13. }
  14. dispatch(addTodo(input.value))
  15. input.value = ''
  16. }}
  17. >
  18. <input ref={node => (input = node)} />
  19. <button type="submit">Add Todo</button>
  20. </form>
  21. </div>
  22. )
  23. }
  24. export default connect()(AddTodo)