Redux without redux - sharing state and one way data flow using only standard react
| // More complex example implementing the classic "todo" application | |
| import React from 'react'; | |
| import ReactDOM from 'react-dom'; | |
| import {StateDispatcher} from './StateDispatcher'; | |
| // Utility function for creating combined reducers. Not necessary, but nice to have | |
| const combineReducers = (reducers) => (state, action) => { | |
| return Object.entries(reducers).reduce((newState, [reducerKey, reducer]) => { | |
| return { | |
| ...newState, | |
| [reducerKey]: reducer(state[reducerKey], action) | |
| } | |
| }, state); | |
| }; | |
| const initialState = { | |
| todos: [ | |
| { | |
| title: 'Do something', | |
| id: -1, | |
| complete: false | |
| } | |
| ], | |
| filter: 'all', | |
| newTodoText: '' | |
| }; | |
| // Used to generate new ids for the todos | |
| let nextId = 0; | |
| /* | |
| Reducers | |
| */ | |
| const todosReducer = (state, action) => { | |
| switch (action.type) { | |
| case 'ADD_TODO': { | |
| return [ | |
| ...state, | |
| { | |
| title: action.payload, | |
| id: nextId++, | |
| complete: false | |
| } | |
| ]; | |
| } | |
| case 'TOGGLE_TODO':{ | |
| const findTodo = todo => todo.id === action.payload; | |
| const todo = state.find(findTodo); | |
| return [ | |
| ...state.filter(todo => !findTodo(todo)), | |
| { | |
| ...todo, | |
| complete: !todo.complete | |
| } | |
| ]; | |
| } | |
| default: return state; | |
| } | |
| } | |
| const filterReducer = (state, action) => { | |
| switch (action.type) { | |
| case 'APPLY_FILTER': return action.payload | |
| default: return state; | |
| } | |
| } | |
| const newTodoTextReducer = (state, action) => { | |
| switch (action.type) { | |
| case 'SET_NEW_TODO_TEXT': return action.payload; | |
| case 'ADD_TODO': return ''; | |
| default: return state; | |
| } | |
| }; | |
| // The final combined reducer | |
| const rootReducer = combineReducers({ | |
| todos: todosReducer, | |
| filter: filterReducer, | |
| newTodoText: newTodoTextReducer | |
| }); | |
| const applyFilter = (filter, todos) => todos.filter(todo => { | |
| switch (filter) { | |
| case 'completed': | |
| return todo.complete; | |
| case 'uncompleted': | |
| return !todo.complete; | |
| default: | |
| return true; | |
| } | |
| }); | |
| ReactDOM.render( | |
| <StateDispatcher state={initialState} reducer={rootReducer}> | |
| {({state, dispatch}) => { | |
| return <div> | |
| <div> | |
| <ul> | |
| {applyFilter(state.filter, state.todos).map(todo => | |
| <li | |
| key={todo.id} | |
| onClick={() => dispatch({type: 'TOGGLE_TODO', payload: todo.id})} | |
| style={{ | |
| textDecoration: todo.complete ? 'line-through' : 'none' | |
| }} | |
| > | |
| {todo.title} | |
| </li> | |
| )} | |
| </ul> | |
| </div> | |
| <div> | |
| <button onClick={() => dispatch({type: 'APPLY_FILTER', payload: 'all'})}>All</button> | |
| <button onClick={() => dispatch({type: 'APPLY_FILTER', payload: 'completed'})}>Completed</button> | |
| <button onClick={() => dispatch({type: 'APPLY_FILTER', payload: 'uncompleted'})}>Uncompleted</button> | |
| </div> | |
| <div> | |
| <input | |
| value={state.newTodoText} | |
| onChange={e => dispatch({type: 'SET_NEW_TODO_TEXT', payload: e.target.value })} | |
| onKeyPress={e => | |
| (e.key === 'Enter' && state.newTodoText) | |
| ? dispatch({type: 'ADD_TODO', payload: state.newTodoText}) | |
| : null | |
| } | |
| /> | |
| <button | |
| onClick={() => state.newTodoText && dispatch({type: 'ADD_TODO', payload: state.newTodoText})} | |
| > | |
| Add Todo | |
| </button> | |
| </div> | |
| </div> | |
| }} | |
| </StateDispatcher>, | |
| document.getElementById('root') | |
| ); |