Skip to content

Instantly share code, notes, and snippets.

@tuffs
Last active August 19, 2018 03:43
Show Gist options
  • Save tuffs/713436238b19973a228424fe77097f10 to your computer and use it in GitHub Desktop.
Save tuffs/713436238b19973a228424fe77097f10 to your computer and use it in GitHub Desktop.
React + Redux
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Todos/Goals</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
<script src='https://unpkg.com/[email protected]/umd/react.development.js'></script>
<script src='https://unpkg.com/[email protected]/umd/react-dom.development.js'></script>
<script src='https://unpkg.com/[email protected]/babel.min.js'></script>
<script type="text/javascript" src="https://tylermcginnis.com/goals-todos-api/index.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/javascript">
function generateId () {
return Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36);
}
// App Code
const ADD_TODO = 'ADD_TODO';
const REMOVE_TODO = 'REMOVE_GOAL';
const TOGGLE_TODO = 'TOGGLE_GOAL';
const ADD_GOAL = 'ADD_GOAL';
const REMOVE_GOAL = 'REMOVE_GOAL';
const TOGGLE_GOAL = 'TOGGLE_GOAL';
const RECEIVE_DATA = 'RECEIVE_DATA';
function addTodoAction (todo) {
return {
type: ADD_TODO,
todo,
};
}
function removeTodoAction (id) {
return {
type: REMOVE_TODO,
id,
};
}
function toggleTodoAction (id) {
return {
type: TOGGLE_TODO,
id,
};
}
function addGoalAction (goal) {
return {
type: ADD_GOAL,
goal,
};
}
function removeGoalAction (id) {
return {
type: REMOVE_GOAL,
id,
};
}
function receiveDataAction (todos, goals) {
return {
type: RECEIVE_DATA,
todos,
goals
}
}
const checker = (store) => (next) => (action) => {
if (
action.type === ADD_TODO &&
action.todo.name.toLowerCase().indexOf('bitcoin') !== -1
) {
return alert("Nope. That's a bad idea.");
}
if (
action.type === ADD_GOAL &&
action.goal.name.toLowerCase().indexOf('bitcoin') !== -1
) {
return alert("Nope. That's a bad idea.");
}
return next(action);
}
const logger = (store) => (next) => (action) => {
console.group(action.type);
console.log('The action: ', action);
const result = next(action);
console.log('The new state: ', store.getState());
console.groupEnd();
return result;
}
// Reducer Functions
function todos (state = [], action) {
switch(action.type) {
case ADD_TODO :
return state.concat([action.todo]);
case REMOVE_TODO :
return state.filter((todo) => todo.id !== action.id);
case TOGGLE_TODO :
return state.map((todo) => todo.id !== action.id ? todo :
Object.assign({}, todo, { complete: !todo.complete})
);
case RECEIVE_DATA :
return action.todos;
default :
return state;
}
}
function goals (state = [], action) {
switch(action.type) {
case ADD_GOAL :
return state.concat([action.goal]);
case REMOVE_GOAL :
return state.filter((goal) => goal.id !== action.id);
case TOGGLE_GOAL :
return state.map((goal) => goal.id !== action.id ? goal :
Object.assign({}, goal, { complete: !goal.complete })
);
case RECEIVE_DATA :
return action.goals;
default:
return state;
}
}
function loading (state = true, action) {
switch(action.type) {
case RECEIVE_DATA :
return false;
default :
return state;
}
}
const store = Redux.createStore(Redux.combineReducers({
todos,
goals,
loading
}), Redux.applyMiddleware(checker, logger));
</script>
<script type="text/babel">
function List (props) {
return (
<ul>
{props.items.map((item) => (
<li key={item.id}>
<span
onClick={() => props.toggle && props.toggle(item.id)}
style={{textDecoration: item.complete ? 'line-through' : 'none'}}
>
{item.name}
</span>
<button onClick={() => props.remove(item)}>X</button>
</li>
))}
</ul>
)
}
class Todos extends React.Component {
removeItem = (todo) => {
this.props.store.dispatch(removeTodoAction(todo.id));
return API.deleteTodo(todo.id)
.catch(() => {
this.props.store.dispatch(addTodoAction(todo));
alert('An error occured. Try again.');
});
}
toggleItem = (id) => {
this.props.store.dispatch(toggleTodoAction(id));
return API.saveTodoToggle(id)
.catch(() => {
this.props.store.dispatch(toggleTodoAction(id));
alert('An error occured. Try again.');
});
}
addItem = (e) => {
e.preventDefault();
name = this.input.value;
return API.saveTodo(name)
.then((todo) => {
this.props.store.dispatch(addTodoAction(todo));
this.input.value = '';
})
.catch(() => {
alert('An error occured. Try again');
this.input.value = name;
});
}
render() {
return (
<div>
<h1>Todo List</h1>
<input
type='text'
placeholder='Add Todo'
ref={(input) => this.input = input}
/>
<button onClick={this.addItem}>Add Todo</button>
<List
toggle={this.toggleItem}
items={this.props.todos}
remove={this.removeItem}
/>
</div>
);
}
}
class Goals extends React.Component {
removeItem = (goal) => {
this.props.store.dispatch(removeGoalAction(goal.id));
return API.deleteGoal(goal.id)
.catch(() => {
this.props.store.dispatch(addGoalAction(goal));
alert('An error occured. Try again.');
});
}
addItem = (e) => {
e.preventDefault();
const name = this.input.value;
this.input.value = '';
this.props.store.dispatch(addGoalAction({
id: generateId(),
name,
complete: false
}));
}
render() {
return (
<div>
<h1>Goal List</h1>
<input
type='text'
placeholder='Add Goal'
ref={(input) => this.input = input}
/>
<button onClick={this.addItem}>Add Goal</button>
<List
items={this.props.goals}
remove={this.removeItem}
/>
</div>
);
}
}
class App extends React.Component {
componentDidMount () {
const { store } = this.props;
Promise.all([
API.fetchTodos(),
API.fetchGoals(),
]).then(([ todos, goals ]) => {
store.dispatch(receiveDataAction(todos, goals));
});
store.subscribe(() => this.forceUpdate());
}
render() {
const { store } = this.props;
const { todos, goals, loading } = store.getState();
if (loading === true) {
return <h3>Loading</h3>
}
return (
<div>
<Todos todos={todos} store={store} />
<Goals goals={goals} store={store} />
</div>
);
}
}
ReactDOM.render(<App store={store} />, document.getElementById('app'));
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment