From fe83020a2fc9f9086c4485840ff11380c3a5518a Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Thu, 7 Jun 2018 15:59:33 +0300 Subject: [PATCH] flatten redux store --- react/src/actions/lists.js | 34 +++++---- react/src/actions/todos.js | 39 ++-------- react/src/containers/AppContainer.js | 5 +- react/src/containers/ListActionsContainer.js | 6 +- react/src/containers/SelectorContainer.js | 8 +- react/src/containers/TodoListContainer.js | 28 +++---- react/src/containers/TodosContainer.js | 7 +- react/src/reducers/index.js | 2 + react/src/reducers/lists.js | 79 +++++++++----------- react/src/reducers/{list.js => todos.js} | 43 +++++++---- 10 files changed, 118 insertions(+), 133 deletions(-) rename react/src/reducers/{list.js => todos.js} (54%) diff --git a/react/src/actions/lists.js b/react/src/actions/lists.js index 00fc764..b56efc3 100644 --- a/react/src/actions/lists.js +++ b/react/src/actions/lists.js @@ -1,4 +1,5 @@ import { API_ROOT, getToken } from './util'; +import { RECIEVE_TODOS } from './todos'; export const ADD_LIST = 'ADD_LIST'; export const REMOVE_LIST = 'REMOVE_LIST'; @@ -42,9 +43,6 @@ export function stopCreateList() { export function stopEditList() { return { type: STOP_EDIT_LIST }; } -function addListToState(list) { - return { type: ADD_LIST, list }; -} export function addList(name) { return async dispatch => { @@ -59,16 +57,12 @@ export function addList(name) { }); const json = await response.json(); const list = json.data; - dispatch(addListToState(list)); + dispatch({ type: ADD_LIST, list }); dispatch(changeList(list.id)); dispatch(validateLists()); }; } -function removeListFromState(id) { - return { type: REMOVE_LIST, id }; -} - export function removeList() { return async (dispatch, getState) => { let state = getState(); @@ -83,7 +77,7 @@ export function removeList() { }); const json = await response.json(); if (json.success) { - dispatch(removeListFromState(list)); + dispatch({ type: REMOVE_LIST, list }); state = getState(); const lists = Object.values(state.lists.lists); const newList = lists.length ? lists[lists.length - 1].id : ''; @@ -93,10 +87,6 @@ export function removeList() { }; } -function editListNameInState(id, name) { - return { type: EDIT_LIST_NAME, id, name }; -} - export function editList(name) { return async (dispatch, getState) => { const state = getState(); @@ -112,12 +102,25 @@ export function editList(name) { }); const json = await response.json(); if (json.success) { - dispatch(editListNameInState(list, name)); + dispatch({ type: EDIT_LIST_NAME, list, name }); } dispatch(validateLists()); }; } +function normalizeTodos(lists) { + return lists.reduce((todos, list) => { + const listTodosObj = list.todos.reduce( + (listTodos, todo) => ({ + ...listTodos, + [todo.id]: { ...todo }, + }), + {}, + ); + return { ...todos, ...listTodosObj }; + }, {}); +} + export function fetchLists() { return async dispatch => { dispatch(requestLists()); @@ -135,11 +138,12 @@ export function fetchLists() { fetching: false, editing: false, ...list, - todos: [...list.todos.reverse()], + todos: list.todos.map(todo => todo.id), }; return newObj; }, {}); + dispatch({ type: RECIEVE_TODOS, todos: normalizeTodos(lists) }); dispatch(recieveLists(listsObj)); if (lists.length !== 0) { dispatch(changeList(listsObj[Object.keys(listsObj)[0]].id)); diff --git a/react/src/actions/todos.js b/react/src/actions/todos.js index 9b4dd95..d105568 100644 --- a/react/src/actions/todos.js +++ b/react/src/actions/todos.js @@ -16,20 +16,10 @@ export const VisibilityFilters = { SHOW_ACTIVE: 'SHOW_ACTIVE', }; -function toggleTodoInList(id) { - return { type: TOGGLE_TODO, id }; -} - export function setVisibilityFilter(filter) { return { type: SET_VISIBILITY_FILTER, filter }; } -function requestTodos(list) { - return { type: REQUEST_TODOS, list }; -} -function recieveTodos(list, todos) { - return { type: RECIEVE_TODOS, list, todos }; -} function invalidateTodos() { return { type: INVALIDATE_TODOS }; } @@ -37,10 +27,6 @@ function validateTodos() { return { type: VALIDATE_TODOS }; } -function addTodoToList(todo) { - return { type: ADD_TODO, todo }; -} - export function addTodo(text) { return async (dispatch, getState) => { const state = getState(); @@ -57,16 +43,12 @@ export function addTodo(text) { }); const json = await response.json(); const todo = json.data; - dispatch(addTodoToList(todo)); + dispatch({ type: ADD_TODO, todo }); dispatch(validateTodos()); } }; } -function removeTodoFromList(id) { - return { type: REMOVE_TODO, id }; -} - export function removeTodo(id) { return async dispatch => { dispatch(invalidateTodos()); @@ -79,7 +61,7 @@ export function removeTodo(id) { }); const json = await response.json(); if (json.success) { - dispatch(removeTodoFromList(id)); + dispatch({ type: REMOVE_TODO, id }); } dispatch(validateTodos()); }; @@ -89,8 +71,7 @@ export function toggleTodo(id) { return async (dispatch, getState) => { dispatch(invalidateTodos()); const state = getState(); - const listObj = state.lists.lists[state.lists.list]; - const todoObj = listObj.todos.find(todo => todo.id === id); + const todoObj = state.todos.todos[id]; const completed = !todoObj.completed; const response = await fetch(`${API_ROOT}/todos/${id}`, { body: JSON.stringify({ completed }), @@ -102,16 +83,12 @@ export function toggleTodo(id) { }); const json = await response.json(); if (json.success) { - dispatch(toggleTodoInList(id)); + dispatch({ type: TOGGLE_TODO, id }); } dispatch(validateTodos()); }; } -function editTodoInList(id, todo) { - return { type: EDIT_TODO, id, todo }; -} - export function editTodo(id, text) { return async dispatch => { dispatch(invalidateTodos()); @@ -126,7 +103,7 @@ export function editTodo(id, text) { const json = await response.json(); if (json.success) { const todo = json.data; - dispatch(editTodoInList(id, todo)); + dispatch({ type: EDIT_TODO, id, todo }); } dispatch(validateTodos()); }; @@ -134,14 +111,14 @@ export function editTodo(id, text) { export function fetchTodos(list) { return async dispatch => { - dispatch(requestTodos(list)); - const response = await fetch(`${API_ROOT}/lists/${list.id}/todos`, { + dispatch({ type: REQUEST_TODOS, list }); + const response = await fetch(`${API_ROOT}/todos`, { headers: { Authorization: `Bearer ${getToken()}`, }, }); const json = await response.json(); const todos = json.data; - dispatch(recieveTodos(list, todos)); + dispatch({ type: RECIEVE_TODOS, todos }); }; } diff --git a/react/src/containers/AppContainer.js b/react/src/containers/AppContainer.js index 0ffefc7..1b0781f 100644 --- a/react/src/containers/AppContainer.js +++ b/react/src/containers/AppContainer.js @@ -16,4 +16,7 @@ function mapDispatchToProps(dispatch) { }; } -export default connect(mapStateToProps, mapDispatchToProps)(App); +export default connect( + mapStateToProps, + mapDispatchToProps, +)(App); diff --git a/react/src/containers/ListActionsContainer.js b/react/src/containers/ListActionsContainer.js index dd1efbe..321af3a 100644 --- a/react/src/containers/ListActionsContainer.js +++ b/react/src/containers/ListActionsContainer.js @@ -9,14 +9,10 @@ import { } from '../actions/lists'; function mapStateToProps(state) { - const editing = - state.lists.list && !state.lists.dirty - ? state.lists.lists[state.lists.list].editing - : false; return { list: state.lists.list, creating: state.lists.creating, - editing, + editing: state.lists.editing, }; } function mapDispatchToProps(dispatch) { diff --git a/react/src/containers/SelectorContainer.js b/react/src/containers/SelectorContainer.js index 5cbc602..7e06047 100644 --- a/react/src/containers/SelectorContainer.js +++ b/react/src/containers/SelectorContainer.js @@ -3,14 +3,10 @@ import Selector from '../components/Selector'; import { changeList, addList, editList } from '../actions/lists'; function mapStateToProps(state) { - const editing = - state.lists.list && !state.lists.dirty - ? state.lists.lists[state.lists.list].editing - : false; return { lists: state.lists, list: state.lists.list, - editing, + editing: state.lists.editing, creating: state.lists.creating, }; } @@ -19,7 +15,7 @@ function mapDispatchToProps(dispatch) { return { onChange: list => dispatch(changeList(list)), addList: name => dispatch(addList(name)), - editList: (id, name) => dispatch(editList(id, name)), + editList: name => dispatch(editList(name)), }; } diff --git a/react/src/containers/TodoListContainer.js b/react/src/containers/TodoListContainer.js index 2464a28..ec09c99 100644 --- a/react/src/containers/TodoListContainer.js +++ b/react/src/containers/TodoListContainer.js @@ -5,23 +5,17 @@ import { toggleTodo, removeTodo, editTodo } from '../actions/todos'; import getVisibleTodos from './getVisibleTodos'; function mapStateToProps(state) { - const { list } = state.lists; - try { - const listObj = state.lists.lists[list]; - const listTodos = state.lists.lists[list].todos; - - return { - list, - todos: getVisibleTodos(listTodos, state.visibilityFilter), - dirty: listObj.dirty, - }; - } catch (e) { - return { - list: '', - todos: [], - dirty: true, - }; - } + return { + todos: state.lists.list + ? getVisibleTodos( + state.lists.lists[state.lists.list].todos.map( + id => state.todos.todos[id], + ), + state.visibilityFilter, + ) + : [], + dirty: state.todos.dirty, + }; } function mapDispatchToProps(dispatch) { diff --git a/react/src/containers/TodosContainer.js b/react/src/containers/TodosContainer.js index 88a59a8..11af045 100644 --- a/react/src/containers/TodosContainer.js +++ b/react/src/containers/TodosContainer.js @@ -17,4 +17,9 @@ function mapDispatchToProps(dispatch) { }; } -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Todos)); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps, + )(Todos), +); diff --git a/react/src/reducers/index.js b/react/src/reducers/index.js index 59c3b0b..4b3897e 100644 --- a/react/src/reducers/index.js +++ b/react/src/reducers/index.js @@ -4,9 +4,11 @@ import { reducer as formReducer } from 'redux-form'; import lists from './lists'; import visibilityFilter from './visibilityFilter'; import user from './user'; +import todos from './todos'; const todoApp = combineReducers({ lists, + todos, visibilityFilter, form: formReducer, user, diff --git a/react/src/reducers/lists.js b/react/src/reducers/lists.js index ac1b6e9..5385c94 100644 --- a/react/src/reducers/lists.js +++ b/react/src/reducers/lists.js @@ -12,17 +12,7 @@ import { STOP_CREATE_LIST, STOP_EDIT_LIST, } from '../actions/lists'; -import { - ADD_TODO, - INVALIDATE_TODOS, - VALIDATE_TODOS, - REQUEST_TODOS, - RECIEVE_TODOS, - REMOVE_TODO, - TOGGLE_TODO, - EDIT_TODO, -} from '../actions/todos'; -import list from './list'; +import { REMOVE_TODO, ADD_TODO } from '../actions/todos'; export default function lists( state = { @@ -32,6 +22,7 @@ export default function lists( loaded: false, creating: false, list: null, + editing: false, }, action, ) { @@ -64,45 +55,60 @@ export default function lists( }; case REMOVE_LIST: { const newLists = { ...state.lists }; - delete newLists[action.id]; + delete newLists[action.list]; return { ...state, + list: null, lists: newLists, }; } case START_EDIT_LIST: { return { ...state, - lists: { - ...state.lists, - [state.list]: { - ...state.lists[state.list], - editing: true, - }, - }, + editing: true, }; } case STOP_EDIT_LIST: { return { ...state, - lists: { - ...state.lists, - [state.list]: { - ...state.lists[state.list], - editing: false, - }, - }, + editing: false, }; } case EDIT_LIST_NAME: { return { ...state, + editing: false, lists: { ...state.lists, - [action.id]: { - ...state.lists[action.id], + [action.list]: { + ...state.lists[action.list], name: action.name, - editing: false, + }, + }, + }; + } + case REMOVE_TODO: { + return { + ...state, + lists: { + ...state.lists, + [state.list]: { + ...state.lists[state.list], + todos: state.lists[state.list].todos.filter( + todo => todo !== action.id, + ), + }, + }, + }; + } + case ADD_TODO: { + return { + ...state, + lists: { + ...state.lists, + [state.list]: { + ...state.lists[state.list], + todos: [action.todo.id, ...state.lists[state.list].todos], }, }, }; @@ -122,21 +128,6 @@ export default function lists( ...state, fetching: true, }; - case RECIEVE_TODOS: - case ADD_TODO: - case EDIT_TODO: - case INVALIDATE_TODOS: - case VALIDATE_TODOS: - case REQUEST_TODOS: - case REMOVE_TODO: - case TOGGLE_TODO: - return { - ...state, - lists: { - ...state.lists, - [state.list]: list(state.lists[state.list], action), - }, - }; default: return state; } diff --git a/react/src/reducers/list.js b/react/src/reducers/todos.js similarity index 54% rename from react/src/reducers/list.js rename to react/src/reducers/todos.js index 419456b..3ee4f2a 100644 --- a/react/src/reducers/list.js +++ b/react/src/reducers/todos.js @@ -8,13 +8,13 @@ import { VALIDATE_TODOS, EDIT_TODO, } from '../actions/todos'; +import { REMOVE_LIST } from '../actions/lists'; export default function todos( state = { dirty: true, fetching: false, todos: null, - editing: false, }, action, ) { @@ -29,7 +29,7 @@ export default function todos( case ADD_TODO: return { ...state, - todos: [action.todo, ...state.todos], + todos: { [action.todo.id]: action.todo, ...state.todos }, }; case INVALIDATE_TODOS: return { @@ -44,29 +44,46 @@ export default function todos( case EDIT_TODO: return { ...state, - todos: state.todos.map( - todo => (todo.id === action.id ? action.todo : todo), - ), + todos: { + ...state.todos, + [action.id]: action.todo, + }, }; case REQUEST_TODOS: return { ...state, fetching: true, }; - case REMOVE_TODO: + case REMOVE_TODO: { + const newTodos = { ...state.todos }; + delete newTodos[action.id]; return { ...state, - todos: state.todos.filter(todo => todo.id !== action.id), + todos: newTodos, }; + } + case REMOVE_LIST: { + const newTodos = { ...state.todos }; + Object.keys(newTodos).forEach(todoId => { + if (newTodos[todoId].list === action.list) { + delete newTodos[todoId]; + } + }); + return { + ...state, + todos: newTodos, + }; + } case TOGGLE_TODO: { return { ...state, - todos: state.todos.map( - todo => - todo.id === action.id - ? { ...todo, completed: !todo.completed } - : todo, - ), + todos: { + ...state.todos, + [action.id]: { + ...state.todos[action.id], + completed: !state.todos[action.id].completed, + }, + }, }; } default: