Skip to content

Commit

Permalink
bootstrapping
Browse files Browse the repository at this point in the history
  • Loading branch information
rwieruch committed Jul 31, 2017
1 parent 7618044 commit 932838a
Showing 1 changed file with 15 additions and 296 deletions.
311 changes: 15 additions & 296 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,22 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { applyMiddleware, combineReducers, createStore } from 'redux';
import { Provider, connect } from 'react-redux';
import { createLogger } from 'redux-logger';
import createSagaMiddleware, { delay } from 'redux-saga';
import { put, takeEvery } from 'redux-saga/effects';
import { schema, normalize } from 'normalizr';
import uuid from 'uuid/v4';
import { combineReducers, createStore } from 'redux';
import './index.css';

// filters

const VISIBILITY_FILTERS = {
SHOW_COMPLETED: item => item.completed,
SHOW_INCOMPLETED: item => !item.completed,
SHOW_ALL: item => true,
};

// schemas

const todoSchema = new schema.Entity('todo');

// action types

const TODO_ADD = 'TODO_ADD';
const TODO_TOGGLE = 'TODO_TOGGLE';
const FILTER_SET = 'FILTER_SET';
const NOTIFICATION_HIDE = 'NOTIFICATION_HIDE';
const TODO_ADD_WITH_NOTIFICATION = 'TODO_ADD_WITH_NOTIFICATION';

// reducers

const todos = [
{ id: uuid(), name: 'Hands On: Snake with Local State' },
{ id: uuid(), name: 'Challenge: Snake with Higher Order Components' },
{ id: uuid(), name: 'Hands On: Redux Standalone with advanced Actions' },
{ id: uuid(), name: 'Hands On: Redux Standalone with advanced Reducers' },
{ id: uuid(), name: 'Hands On: Bootstrap App with Redux' },
{ id: uuid(), name: 'Hands On: Naive Todo with React and Redux' },
{ id: uuid(), name: 'Hands On: Sophisticated Todo with React and Redux' },
{ id: uuid(), name: 'Hands On: Connecting State Everywhere' },
{ id: uuid(), name: 'Challenge: Snake with React and Redux' },
{ id: uuid(), name: 'Hands On: Todo with advanced Redux' },
{ id: uuid(), name: 'Hands On: Todo with more Features' },
{ id: uuid(), name: 'Challenge: Snake with Redux' },
{ id: uuid(), name: 'Hands On: Todo with Notifications' },
{ id: uuid(), name: 'Challenge: Snake with Redux and Async Actions' },
{ id: uuid(), name: 'Hands On: Hacker News with Redux' },
{ id: uuid(), name: 'Challenge: Hacker News with beyond Redux' },
{ id: uuid(), name: 'Challenge: Hacker News with beyond Redux' },
{ id: uuid(), name: 'Hands On: Snake with MobX' },
{ id: uuid(), name: 'Hands On: Todo App with MobX' },
{ id: uuid(), name: 'Challenge: Hacker News App with MobX' },
{ id: uuid(), name: 'Challenge: Consuming a GrapQL API with Relay' },
{ id: '0', name: 'learn redux' },
{ id: '1', name: 'learn mobx' },
];

const normalizedTodos = normalize(todos, [todoSchema]);

const initialTodoState = {
entities: normalizedTodos.entities.todo,
ids: normalizedTodos.result,
};

function todoReducer(state = initialTodoState, action) {
function todoReducer(state = todos, action) {
switch(action.type) {
case TODO_ADD : {
return applyAddTodo(state, action);
Expand All @@ -75,18 +29,16 @@ function todoReducer(state = initialTodoState, action) {
}

function applyAddTodo(state, action) {
const todo = { ...action.todo, completed: false };
const entities = { ...state.entities, [todo.id]: todo };
const ids = [ ...state.ids, action.todo.id ];
return { ...state, entities, ids };
const todo = Object.assign({}, action.todo, { completed: false });
return state.concat(todo);
}

function applyToggleTodo(state, action) {
const id = action.todo.id;
const todo = state.entities[id];
const toggledTodo = { ...todo, completed: !todo.completed };
const entities = { ...state.entities, [id]: toggledTodo };
return { ...state, entities };
return state.map(todo =>
todo.id === action.todo.id
? Object.assign({}, todo, { completed: !todo.completed })
: todo
);
}

function filterReducer(state = 'SHOW_ALL', action) {
Expand All @@ -102,47 +54,8 @@ function applySetFilter(state, action) {
return action.filter;
}

function notificationReducer(state = {}, action) {
switch(action.type) {
case TODO_ADD : {
return applySetNotifyAboutAddTodo(state, action);
}
case NOTIFICATION_HIDE : {
return applyRemoveNotification(state, action);
}
default : return state;
}
}

function applySetNotifyAboutAddTodo(state, action) {
const { name, id } = action.todo;
return { ...state, [id]: 'Todo Created: ' + name };
}

function applyRemoveNotification(state, action) {
const {
[action.id]: notificationToRemove,
...restNotifications,
} = state;
return restNotifications;
}

// action creators

function doAddTodoWithNotification(id, name) {
return {
type: TODO_ADD_WITH_NOTIFICATION,
todo: { id, name },
};
}

function doHideNotification(id) {
return {
type: NOTIFICATION_HIDE,
id
};
}

function doAddTodo(id, name) {
return {
type: TODO_ADD,
Expand All @@ -164,213 +77,19 @@ function doSetFilter(filter) {
};
}

// selectors

function getTodosAsIds(state) {
return state.todoState.ids
.map(id => state.todoState.entities[id])
.filter(VISIBILITY_FILTERS[state.filterState])
.map(todo => todo.id);
}

function getTodo(state, todoId) {
return state.todoState.entities[todoId];
}

function getNotifications(state) {
return getArrayOfObject(state.notificationState);
}

function getArrayOfObject(object) {
return Object.keys(object).map(key => object[key]);
}

// sagas

function* watchAddTodoWithNotification() {
yield takeEvery(TODO_ADD_WITH_NOTIFICATION, handleAddTodoWithNotification);
}

function* handleAddTodoWithNotification(action) {
const { todo } = action;
const { id, name } = todo;
yield put(doAddTodo(id, name));
yield delay(5000);
yield put(doHideNotification(id));
}

// store

const rootReducer = combineReducers({
todoState: todoReducer,
filterState: filterReducer,
notificationState: notificationReducer,
});

const logger = createLogger();
const saga = createSagaMiddleware();
const store = createStore(rootReducer);

const store = createStore(
rootReducer,
undefined,
applyMiddleware(saga, logger)
);

saga.run(watchAddTodoWithNotification);

// components
// view layer

function TodoApp() {
return (
<div>
<ConnectedFilter />
<ConnectedTodoCreate />
<ConnectedTodoList />
<ConnectedNotifications />
</div>
);
}

function Notifications({ notifications }) {
return (
<div>
{notifications.map(note => <div key={note}>{note}</div>)}
</div>
);
}

function Filter({ onSetFilter }) {
return (
<div>
Show
<button
type="text"
onClick={() => onSetFilter('SHOW_ALL')}>
All</button>
<button
type="text"
onClick={() => onSetFilter('SHOW_COMPLETED')}>
Completed</button>
<button
type="text"
onClick={() => onSetFilter('SHOW_INCOMPLETED')}>
Incompleted</button>
</div>
);
}

class TodoCreate extends React.Component {
constructor(props) {
super(props);

this.state = {
value: '',
};

this.onCreateTodo = this.onCreateTodo.bind(this);
this.onChangeName = this.onChangeName.bind(this);
}

onChangeName(event) {
this.setState({ value: event.target.value });
}

onCreateTodo(event) {
this.props.onAddTodo(this.state.value);
this.setState({ value: '' });
event.preventDefault();
}

render() {
return (
<div>
<form onSubmit={this.onCreateTodo}>
<input
type="text"
placeholder="Add Todo..."
value={this.state.value}
onChange={this.onChangeName}
/>
<button type="submit">Add</button>
</form>
</div>
);
}
}

function TodoList({ todosAsIds }) {
return (
<div>
{todosAsIds.map(todoId => <ConnectedTodoItem
key={todoId}
todoId={todoId}
/>)}
</div>
);
return <div>Todo App</div>;
}

function TodoItem({ todo, onToggleTodo }) {
const { name, id, completed } = todo;
return (
<div>
{name}
<button
type="button"
onClick={() => onToggleTodo(id)}
>
{completed ? "Incomplete" : "Complete"}
</button>
</div>
);
}

// Connecting React and Redux

function mapStateToPropsList(state) {
return {
todosAsIds: getTodosAsIds(state),
};
}

function mapStateToPropsItem(state, props) {
return {
todo: getTodo(state, props.todoId),
};
}

function mapDispatchToPropsItem(dispatch) {
return {
onToggleTodo: id => dispatch(doToggleTodo(id)),
};
}

function mapDispatchToPropsCreate(dispatch) {
return {
onAddTodo: name => dispatch(doAddTodoWithNotification(uuid(), name)),
};
}

function mapDispatchToPropsFilter(dispatch) {
return {
onSetFilter: filterType => dispatch(doSetFilter(filterType)),
};
}

function mapStateToPropsNotifications(state, props) {
return {
notifications: getNotifications(state),
};
}

const ConnectedTodoList = connect(mapStateToPropsList)(TodoList);
const ConnectedTodoItem = connect(mapStateToPropsItem, mapDispatchToPropsItem)(TodoItem);
const ConnectedTodoCreate = connect(null, mapDispatchToPropsCreate)(TodoCreate);
const ConnectedFilter = connect(null, mapDispatchToPropsFilter)(Filter);
const ConnectedNotifications = connect(mapStateToPropsNotifications)(Notifications);

ReactDOM.render(
<Provider store={store}>
<TodoApp />
</Provider>,
document.getElementById('root')
);
ReactDOM.render(<TodoApp />, document.getElementById('root'));

0 comments on commit 932838a

Please sign in to comment.