Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 79 additions & 10 deletions app/assets/javascripts/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,93 @@ import 'whatwg-fetch';

// TYPES
export const TODO_ITEMS_LOADING = 'TODO_ITEMS_LOADING';
export const TODO_CREATE_ITEM = 'TODO_CREATE_ITEM';
export const TODO_UPDATE_ITEM = 'TODO_UPDATE_ITEM';
export const TODO_SOFT_UPDATE_ITEM = 'TODO_SOFT_UPDATE_ITEM';
export const TODO_DELETE_ITEM = 'TODO_DELETE_ITEM';
export const TODO_ITEMS_RECEIVED = 'TODO_ITEMS_RECEIVED';
export const TODO_ITEMS_FAILURE = 'TODO_ITEMS_FAILURE';

// ACTION CREATORS
export function load() {
return async (dispatch) => {
try {
dispatch({ type: TODO_ITEMS_LOADING });

const response = await fetch('/todo_items', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
const data = await response.json();
dispatch({ type: TODO_ITEMS_RECEIVED, payload: { items: data } });
} catch (e) {
console.error(e);
}
};
}

export function create(todo) {
return async (dispatch) => {
try {
const response = await fetch(
'/todo_items',
{
method: 'POST',
body: JSON.stringify({ todo_item: todo }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
dispatch({ type: TODO_CREATE_ITEM });

const response = await fetch('/todo_items', {
method: 'POST',
body: JSON.stringify({ todo_item: todo }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
const data = await response.json();
dispatch({ type: TODO_ITEMS_RECEIVED, payload: { items: data } });
} catch (e) {
console.error(e);
}
};
}

export function update(todo) {
return async (dispatch) => {
try {
dispatch({ type: TODO_UPDATE_ITEM });

const response = await fetch('/todo_items', {
method: 'PUT',
body: JSON.stringify({ todo_item: todo }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
});
const data = await response.json();
dispatch({ type: TODO_ITEMS_RECEIVED, payload: { items: data } });
} catch (e) {
console.error(e);
}
};
}

export function softUpdateItem(options) {
return { type: TODO_SOFT_UPDATE_ITEM, payload: options };
}

export function deleteItem(todo) {
return async (dispatch) => {
try {
dispatch({ type: TODO_DELETE_ITEM });

const response = await fetch('/todo_items', {
method: 'DELETE',
body: JSON.stringify({ todo_item: todo }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
);
});
const data = await response.json();
dispatch({ type: TODO_ITEMS_RECEIVED, payload: { items: data } });
} catch (e) {
Expand Down
81 changes: 64 additions & 17 deletions app/assets/javascripts/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { render } from 'react-dom';
import PropTypes from 'prop-types';
import { connect, Provider } from 'react-redux';
import 'whatwg-fetch';
import { create } from './actions';
import {
load as loadItems,
create as createItem,
update as updateItem,
softUpdateItem,
deleteItem,
} from './actions';
import configureStore from './configureStore';

// TODO:
Expand All @@ -16,7 +22,11 @@ import configureStore from './configureStore';

class App extends Component {
static propTypes = {
create: PropTypes.func.isRequired,
createItem: PropTypes.func.isRequired,
loadItems: PropTypes.func.isRequired,
updateItem: PropTypes.func.isRequired,
softUpdateItem: PropTypes.func.isRequired,
deleteItem: PropTypes.func.isRequired,
newForm: PropTypes.shape({
text: PropTypes.string,
}),
Expand All @@ -33,44 +43,75 @@ class App extends Component {
super(props);

this.state = {
data: [],
newForm: { ...this.props.newForm },
};
}

async componentWillMount() {
const response = await fetch('/todo_items');
const data = await response.json();

this.setState({ data });
this.props.loadItems();
}

handleNewChange = (e) => {
this.setState({
newForm: { ...this.state.newForm, text: e.target.value },
});
}
};

handleNewKeyUp = (e) => {
if (e.keyCode === 13) {
this.props.create(this.state.newForm);
this.props.createItem(this.state.newForm);
this.setState({ newForm: { ...this.props.newForm } });
}
};

handleEditChange = idx => (e) => {
const modifiedItems = this.props.todos.items;
modifiedItems[idx] = {
...modifiedItems[idx],
text: e.target.value,
};

this.props.softUpdateItem({ items: modifiedItems });
}

render() {
// TODO: Once the data is loaded from Redux completely, remove this
const todos = this.props.todos.items.length ? this.props.todos.items : this.state.data;
handleEditKeyUp = idx => (e) => {
if (e.keyCode === 13) {
this.props.updateItem(this.props.todos.items[idx]);
}
};

itemChecked = idx => () => {
const item = this.props.todos.items[idx];
this.props.updateItem({
...item,
is_done: !item.is_done,
});
}

deleteItem = idx => () => {
this.props.deleteItem(this.props.todos.items[idx]);
}

render() {
return (
<div>
<h1>To Do Is Cool</h1>
<ul>
{todos.map(item => (
{this.props.todos.items.map((item, idx) => (
<li key={item.id}>
<input type="checkbox" checked={item.is_done} value />
<input type="text" value={item.text} />
<button>Delete</button>
<input
type="checkbox"
value={item.is_done || ''}
checked={item.is_done}
onClick={this.itemChecked(idx)}
/>
<input
type="text"
value={item.text}
onChange={this.handleEditChange(idx)}
onKeyUp={this.handleEditKeyUp(idx)}
/>
<button onClick={this.deleteItem(idx)}>Delete</button>
</li>
))}
<li>
Expand All @@ -88,7 +129,13 @@ class App extends Component {
}
}

const ConnectedApp = connect(state => ({ todos: state.todos }), { create })(App);
const ConnectedApp = connect(state => ({ todos: state.todos }), {
loadItems,
createItem,
updateItem,
softUpdateItem,
deleteItem,
})(App);

render(
<Provider store={configureStore()}>
Expand Down
8 changes: 7 additions & 1 deletion app/assets/javascripts/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import {
TODO_ITEMS_LOADING,
TODO_ITEMS_RECEIVED,
TODO_ITEMS_FAILURE,
TODO_SOFT_UPDATE_ITEM,
} from './actions';

const initialState = {
export const initialState = {
items: [],
};

Expand All @@ -17,6 +18,11 @@ export default function todosReducer(state = initialState, { type, payload }) {
...state,
items: payload.items,
};
case TODO_SOFT_UPDATE_ITEM:
return {
...state,
items: payload.items,
};
case TODO_ITEMS_FAILURE:
return state;
default:
Expand Down
14 changes: 14 additions & 0 deletions app/controllers/todo_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ def create
index
end

def update
todo_item = TodoItem.find(params['todo_item']['id'])
todo_item.update(todo_item_params)

index
end

def destroy
todo_item = TodoItem.find(params['todo_item']['id'])
todo_item.destroy

index
end

private

def todo_item_params
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
scope 'todo_items' do
get '/' => 'todo_items#index'
post '/' => 'todo_items#create'
put '/' => 'todo_items#update'
delete '/' => 'todo_items#destroy'
end

root to: 'welcome#index'
Expand Down
Loading