flatten redux store

This commit is contained in:
2018-06-07 15:59:33 +03:00
parent e995b6cdbf
commit fe83020a2f
10 changed files with 118 additions and 133 deletions

View File

@@ -1,4 +1,5 @@
import { API_ROOT, getToken } from './util'; import { API_ROOT, getToken } from './util';
import { RECIEVE_TODOS } from './todos';
export const ADD_LIST = 'ADD_LIST'; export const ADD_LIST = 'ADD_LIST';
export const REMOVE_LIST = 'REMOVE_LIST'; export const REMOVE_LIST = 'REMOVE_LIST';
@@ -42,9 +43,6 @@ export function stopCreateList() {
export function stopEditList() { export function stopEditList() {
return { type: STOP_EDIT_LIST }; return { type: STOP_EDIT_LIST };
} }
function addListToState(list) {
return { type: ADD_LIST, list };
}
export function addList(name) { export function addList(name) {
return async dispatch => { return async dispatch => {
@@ -59,16 +57,12 @@ export function addList(name) {
}); });
const json = await response.json(); const json = await response.json();
const list = json.data; const list = json.data;
dispatch(addListToState(list)); dispatch({ type: ADD_LIST, list });
dispatch(changeList(list.id)); dispatch(changeList(list.id));
dispatch(validateLists()); dispatch(validateLists());
}; };
} }
function removeListFromState(id) {
return { type: REMOVE_LIST, id };
}
export function removeList() { export function removeList() {
return async (dispatch, getState) => { return async (dispatch, getState) => {
let state = getState(); let state = getState();
@@ -83,7 +77,7 @@ export function removeList() {
}); });
const json = await response.json(); const json = await response.json();
if (json.success) { if (json.success) {
dispatch(removeListFromState(list)); dispatch({ type: REMOVE_LIST, list });
state = getState(); state = getState();
const lists = Object.values(state.lists.lists); const lists = Object.values(state.lists.lists);
const newList = lists.length ? lists[lists.length - 1].id : ''; 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) { export function editList(name) {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
@@ -112,12 +102,25 @@ export function editList(name) {
}); });
const json = await response.json(); const json = await response.json();
if (json.success) { if (json.success) {
dispatch(editListNameInState(list, name)); dispatch({ type: EDIT_LIST_NAME, list, name });
} }
dispatch(validateLists()); 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() { export function fetchLists() {
return async dispatch => { return async dispatch => {
dispatch(requestLists()); dispatch(requestLists());
@@ -135,11 +138,12 @@ export function fetchLists() {
fetching: false, fetching: false,
editing: false, editing: false,
...list, ...list,
todos: [...list.todos.reverse()], todos: list.todos.map(todo => todo.id),
}; };
return newObj; return newObj;
}, {}); }, {});
dispatch({ type: RECIEVE_TODOS, todos: normalizeTodos(lists) });
dispatch(recieveLists(listsObj)); dispatch(recieveLists(listsObj));
if (lists.length !== 0) { if (lists.length !== 0) {
dispatch(changeList(listsObj[Object.keys(listsObj)[0]].id)); dispatch(changeList(listsObj[Object.keys(listsObj)[0]].id));

View File

@@ -16,20 +16,10 @@ export const VisibilityFilters = {
SHOW_ACTIVE: 'SHOW_ACTIVE', SHOW_ACTIVE: 'SHOW_ACTIVE',
}; };
function toggleTodoInList(id) {
return { type: TOGGLE_TODO, id };
}
export function setVisibilityFilter(filter) { export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, 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() { function invalidateTodos() {
return { type: INVALIDATE_TODOS }; return { type: INVALIDATE_TODOS };
} }
@@ -37,10 +27,6 @@ function validateTodos() {
return { type: VALIDATE_TODOS }; return { type: VALIDATE_TODOS };
} }
function addTodoToList(todo) {
return { type: ADD_TODO, todo };
}
export function addTodo(text) { export function addTodo(text) {
return async (dispatch, getState) => { return async (dispatch, getState) => {
const state = getState(); const state = getState();
@@ -57,16 +43,12 @@ export function addTodo(text) {
}); });
const json = await response.json(); const json = await response.json();
const todo = json.data; const todo = json.data;
dispatch(addTodoToList(todo)); dispatch({ type: ADD_TODO, todo });
dispatch(validateTodos()); dispatch(validateTodos());
} }
}; };
} }
function removeTodoFromList(id) {
return { type: REMOVE_TODO, id };
}
export function removeTodo(id) { export function removeTodo(id) {
return async dispatch => { return async dispatch => {
dispatch(invalidateTodos()); dispatch(invalidateTodos());
@@ -79,7 +61,7 @@ export function removeTodo(id) {
}); });
const json = await response.json(); const json = await response.json();
if (json.success) { if (json.success) {
dispatch(removeTodoFromList(id)); dispatch({ type: REMOVE_TODO, id });
} }
dispatch(validateTodos()); dispatch(validateTodos());
}; };
@@ -89,8 +71,7 @@ export function toggleTodo(id) {
return async (dispatch, getState) => { return async (dispatch, getState) => {
dispatch(invalidateTodos()); dispatch(invalidateTodos());
const state = getState(); const state = getState();
const listObj = state.lists.lists[state.lists.list]; const todoObj = state.todos.todos[id];
const todoObj = listObj.todos.find(todo => todo.id === id);
const completed = !todoObj.completed; const completed = !todoObj.completed;
const response = await fetch(`${API_ROOT}/todos/${id}`, { const response = await fetch(`${API_ROOT}/todos/${id}`, {
body: JSON.stringify({ completed }), body: JSON.stringify({ completed }),
@@ -102,16 +83,12 @@ export function toggleTodo(id) {
}); });
const json = await response.json(); const json = await response.json();
if (json.success) { if (json.success) {
dispatch(toggleTodoInList(id)); dispatch({ type: TOGGLE_TODO, id });
} }
dispatch(validateTodos()); dispatch(validateTodos());
}; };
} }
function editTodoInList(id, todo) {
return { type: EDIT_TODO, id, todo };
}
export function editTodo(id, text) { export function editTodo(id, text) {
return async dispatch => { return async dispatch => {
dispatch(invalidateTodos()); dispatch(invalidateTodos());
@@ -126,7 +103,7 @@ export function editTodo(id, text) {
const json = await response.json(); const json = await response.json();
if (json.success) { if (json.success) {
const todo = json.data; const todo = json.data;
dispatch(editTodoInList(id, todo)); dispatch({ type: EDIT_TODO, id, todo });
} }
dispatch(validateTodos()); dispatch(validateTodos());
}; };
@@ -134,14 +111,14 @@ export function editTodo(id, text) {
export function fetchTodos(list) { export function fetchTodos(list) {
return async dispatch => { return async dispatch => {
dispatch(requestTodos(list)); dispatch({ type: REQUEST_TODOS, list });
const response = await fetch(`${API_ROOT}/lists/${list.id}/todos`, { const response = await fetch(`${API_ROOT}/todos`, {
headers: { headers: {
Authorization: `Bearer ${getToken()}`, Authorization: `Bearer ${getToken()}`,
}, },
}); });
const json = await response.json(); const json = await response.json();
const todos = json.data; const todos = json.data;
dispatch(recieveTodos(list, todos)); dispatch({ type: RECIEVE_TODOS, todos });
}; };
} }

View File

@@ -16,4 +16,7 @@ function mapDispatchToProps(dispatch) {
}; };
} }
export default connect(mapStateToProps, mapDispatchToProps)(App); export default connect(
mapStateToProps,
mapDispatchToProps,
)(App);

View File

@@ -9,14 +9,10 @@ import {
} from '../actions/lists'; } from '../actions/lists';
function mapStateToProps(state) { function mapStateToProps(state) {
const editing =
state.lists.list && !state.lists.dirty
? state.lists.lists[state.lists.list].editing
: false;
return { return {
list: state.lists.list, list: state.lists.list,
creating: state.lists.creating, creating: state.lists.creating,
editing, editing: state.lists.editing,
}; };
} }
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {

View File

@@ -3,14 +3,10 @@ import Selector from '../components/Selector';
import { changeList, addList, editList } from '../actions/lists'; import { changeList, addList, editList } from '../actions/lists';
function mapStateToProps(state) { function mapStateToProps(state) {
const editing =
state.lists.list && !state.lists.dirty
? state.lists.lists[state.lists.list].editing
: false;
return { return {
lists: state.lists, lists: state.lists,
list: state.lists.list, list: state.lists.list,
editing, editing: state.lists.editing,
creating: state.lists.creating, creating: state.lists.creating,
}; };
} }
@@ -19,7 +15,7 @@ function mapDispatchToProps(dispatch) {
return { return {
onChange: list => dispatch(changeList(list)), onChange: list => dispatch(changeList(list)),
addList: name => dispatch(addList(name)), addList: name => dispatch(addList(name)),
editList: (id, name) => dispatch(editList(id, name)), editList: name => dispatch(editList(name)),
}; };
} }

View File

@@ -5,23 +5,17 @@ import { toggleTodo, removeTodo, editTodo } from '../actions/todos';
import getVisibleTodos from './getVisibleTodos'; import getVisibleTodos from './getVisibleTodos';
function mapStateToProps(state) { function mapStateToProps(state) {
const { list } = state.lists; return {
try { todos: state.lists.list
const listObj = state.lists.lists[list]; ? getVisibleTodos(
const listTodos = state.lists.lists[list].todos; state.lists.lists[state.lists.list].todos.map(
id => state.todos.todos[id],
return { ),
list, state.visibilityFilter,
todos: getVisibleTodos(listTodos, state.visibilityFilter), )
dirty: listObj.dirty, : [],
}; dirty: state.todos.dirty,
} catch (e) { };
return {
list: '',
todos: [],
dirty: true,
};
}
} }
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {

View File

@@ -17,4 +17,9 @@ function mapDispatchToProps(dispatch) {
}; };
} }
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Todos)); export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps,
)(Todos),
);

View File

@@ -4,9 +4,11 @@ import { reducer as formReducer } from 'redux-form';
import lists from './lists'; import lists from './lists';
import visibilityFilter from './visibilityFilter'; import visibilityFilter from './visibilityFilter';
import user from './user'; import user from './user';
import todos from './todos';
const todoApp = combineReducers({ const todoApp = combineReducers({
lists, lists,
todos,
visibilityFilter, visibilityFilter,
form: formReducer, form: formReducer,
user, user,

View File

@@ -12,17 +12,7 @@ import {
STOP_CREATE_LIST, STOP_CREATE_LIST,
STOP_EDIT_LIST, STOP_EDIT_LIST,
} from '../actions/lists'; } from '../actions/lists';
import { import { REMOVE_TODO, ADD_TODO } from '../actions/todos';
ADD_TODO,
INVALIDATE_TODOS,
VALIDATE_TODOS,
REQUEST_TODOS,
RECIEVE_TODOS,
REMOVE_TODO,
TOGGLE_TODO,
EDIT_TODO,
} from '../actions/todos';
import list from './list';
export default function lists( export default function lists(
state = { state = {
@@ -32,6 +22,7 @@ export default function lists(
loaded: false, loaded: false,
creating: false, creating: false,
list: null, list: null,
editing: false,
}, },
action, action,
) { ) {
@@ -64,45 +55,60 @@ export default function lists(
}; };
case REMOVE_LIST: { case REMOVE_LIST: {
const newLists = { ...state.lists }; const newLists = { ...state.lists };
delete newLists[action.id]; delete newLists[action.list];
return { return {
...state, ...state,
list: null,
lists: newLists, lists: newLists,
}; };
} }
case START_EDIT_LIST: { case START_EDIT_LIST: {
return { return {
...state, ...state,
lists: { editing: true,
...state.lists,
[state.list]: {
...state.lists[state.list],
editing: true,
},
},
}; };
} }
case STOP_EDIT_LIST: { case STOP_EDIT_LIST: {
return { return {
...state, ...state,
lists: { editing: false,
...state.lists,
[state.list]: {
...state.lists[state.list],
editing: false,
},
},
}; };
} }
case EDIT_LIST_NAME: { case EDIT_LIST_NAME: {
return { return {
...state, ...state,
editing: false,
lists: { lists: {
...state.lists, ...state.lists,
[action.id]: { [action.list]: {
...state.lists[action.id], ...state.lists[action.list],
name: action.name, 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, ...state,
fetching: true, 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: default:
return state; return state;
} }

View File

@@ -8,13 +8,13 @@ import {
VALIDATE_TODOS, VALIDATE_TODOS,
EDIT_TODO, EDIT_TODO,
} from '../actions/todos'; } from '../actions/todos';
import { REMOVE_LIST } from '../actions/lists';
export default function todos( export default function todos(
state = { state = {
dirty: true, dirty: true,
fetching: false, fetching: false,
todos: null, todos: null,
editing: false,
}, },
action, action,
) { ) {
@@ -29,7 +29,7 @@ export default function todos(
case ADD_TODO: case ADD_TODO:
return { return {
...state, ...state,
todos: [action.todo, ...state.todos], todos: { [action.todo.id]: action.todo, ...state.todos },
}; };
case INVALIDATE_TODOS: case INVALIDATE_TODOS:
return { return {
@@ -44,29 +44,46 @@ export default function todos(
case EDIT_TODO: case EDIT_TODO:
return { return {
...state, ...state,
todos: state.todos.map( todos: {
todo => (todo.id === action.id ? action.todo : todo), ...state.todos,
), [action.id]: action.todo,
},
}; };
case REQUEST_TODOS: case REQUEST_TODOS:
return { return {
...state, ...state,
fetching: true, fetching: true,
}; };
case REMOVE_TODO: case REMOVE_TODO: {
const newTodos = { ...state.todos };
delete newTodos[action.id];
return { return {
...state, ...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: { case TOGGLE_TODO: {
return { return {
...state, ...state,
todos: state.todos.map( todos: {
todo => ...state.todos,
todo.id === action.id [action.id]: {
? { ...todo, completed: !todo.completed } ...state.todos[action.id],
: todo, completed: !state.todos[action.id].completed,
), },
},
}; };
} }
default: default: