netlify-faunadb-example/src/App.js
2018-06-13 11:30:54 -07:00

274 lines
7.8 KiB
JavaScript

import React, { Component } from 'react'
import logo from './logo.svg'
import github from './github.svg'
import deployButton from './deploy-to-netlify.svg'
import ContentEditable from './components/ContentEditable'
import './App.css'
class App extends Component {
state = {
todos: []
}
componentDidMount() {
console.log('fetch items')
fetch('/.netlify/functions/todos-read-all')
.then((response) => {
console.log(response)
return response.json()
})
.then((myJson) => {
console.log(myJson)
this.setState({
todos: myJson
})
})
}
saveTodo = (e) => {
e.preventDefault();
const { todos } = this.state
const todoValue = this.inputElement.value
if (!todoValue) {
alert('Please add todo text')
return false
}
// reset input to empty
this.inputElement.value = ''
// Optimistically add todo to UI
const temporaryValue = {
data: {
title: todoValue
}
}
const optimisticTodoState = todos.concat(temporaryValue)
this.setState({
todos: optimisticTodoState
})
// Make API request to create new todo
fetch('/.netlify/functions/todos-create', {
body: JSON.stringify({
title: todoValue
}),
method: 'POST',
// mode: 'cors', // no-cors, cors, *same-origin
})
.then(response => response.json())
.then((json) => {
console.log(json)
// remove temporaryValue from state and persist API response
const persistedState = removeOptimisticTodo(todos).concat(json)
// Set persisted value to state
this.setState({
todos: persistedState
})
// fin
}).catch((e) => {
console.log('An API error occurred', e)
const revertedState = removeOptimisticTodo(todos)
// Reset to original state
this.setState({
todos: revertedState
})
})
}
deleteTodo = (e) => {
const { todos } = this.state
const todoId = e.target.dataset.id
console.log(`Delete todo ${todoId}`)
// Optimistically remove todo from UI
const filteredTodos = todos.reduce((acc, current) => {
const currentId = getTodoId(current)
if (currentId === todoId) {
// save item being removed for rollback
acc.rollbackTodo = current
return acc
}
// filter deleted todo out of the todos list
acc.optimisticState = acc.optimisticState.concat(current)
return acc
}, {
rollbackTodo: {},
optimisticState: []
})
this.setState({
todos: filteredTodos.optimisticState
})
// Make API request to delete todo
fetch(`/.netlify/functions/todos-delete/${todoId}`, {
method: 'POST',
})
.then(response => response.json())
.then((json) => {
console.log(json)
}).catch((e) => {
console.log(`There was an error removing ${todoId}`, e)
// Add item removed back to list
this.setState({
todos: filteredTodos.optimisticState.concat(filteredTodos.rollbackTodo)
})
})
}
updateTodo = (id, todoValue) => {
// Make API request to update todo
fetch(`/.netlify/functions/todos-update/${id}`, {
body: JSON.stringify({
title: todoValue
}),
method: 'POST',
// mode: 'cors', // no-cors, cors, *same-origin
})
.then(response => response.json())
.then((json) => {
console.log(json)
// fin
}).catch((e) => {
console.log('An API error occurred', e)
})
}
handleTodoToggle = (e) => {
console.log('checkbox changed')
console.log(e.target.checked);
}
handleDataChange = (event, currentValue) => {
// save on change debounced?
}
handleBlur = (event, currentValue) => {
let isDifferent = false
const key = event.target.dataset.key
const updatedTodos = this.state.todos.map((todo, i) => {
const id = getTodoId(todo)
if (id === key && todo.data.title !== currentValue) {
todo.data.title = currentValue
isDifferent = true
}
return todo
})
// only set state if input different
if (isDifferent) {
this.setState({
todos: updatedTodos
}, () => {
this.updateTodo(key, currentValue)
})
}
}
renderTodos() {
const { todos } = this.state
return todos.map((todo, i) => {
const { data, ref } = todo
const id = getTodoId(todo)
// only show delete button after create API response returns
let deleteButton
if (ref) {
deleteButton = (
<button data-id={id} onClick={this.deleteTodo}>
delete
</button>
)
}
return (
<div key={i} className='todo-item'>
<label className="todo">
<input className="todo__state" type="checkbox" onChange={this.handleTodoToggle} />
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 25" className="todo__icon">
<use xlinkHref="#todo__box" className="todo__box"></use>
<use xlinkHref="#todo__check" className="todo__check"></use>
<use xlinkHref="#todo__circle" className="todo__circle"></use>
</svg>
<div className='todo-list-title'>
<ContentEditable
tagName='span'
editKey={id}
onChange={this.handleDataChange} // handle innerHTML change
onBlur={this.handleBlur} // handle innerHTML change
html={data.title}
>
</ContentEditable>
</div>
</label>
{deleteButton}
</div>
)
})
}
render() {
return (
<div className='app'>
<header className='app-header'>
<div className='app-title-wrapper'>
<div className='app-title-wrapper'>
<div className='app-left-nav'>
<img src={logo} className='app-logo' alt='logo' />
<div className='app-title-text'>
<h1 className='app-title'>Netlify + Fauna DB</h1>
<p className='app-intro'>
Using FaunaDB & Netlify functions
</p>
</div>
</div>
</div>
<div className='deploy-button-wrapper'>
<a
target='_blank'
rel='noopener noreferrer'
href='https://app.netlify.com/start/deploy?repository=https://github.com/netlify/netlify-faunadb-example'>
<img src={deployButton} className='deploy-button' alt='deploy to netlify' />
</a>
<div className='view-src'>
<a
target='_blank'
rel='noopener noreferrer'
href='https://github.com/netlify/netlify-faunadb-example'>
<img className='github-icon' src={github} alt='view repo on github' /> View the source Luke
</a>
</div>
</div>
</div>
</header>
<div className='todo-list'>
<h2>Create todo</h2>
<form className='todo-create-wrapper' onSubmit={this.saveTodo}>
<input
className='todo-create-input'
placeholder='Add a todo item'
name='name'
ref={el => this.inputElement = el}
autoComplete='off'
style={{marginRight: 20}}
/>
<button className='todo-create-button'>
Create todo
</button>
</form>
{this.renderTodos()}
</div>
</div>
)
}
}
function removeOptimisticTodo(todos) {
// return all 'real' todos
return todos.filter((todo) => {
return todo.ref
})
}
function getTodoId(todo) {
if (!todo.ref) {
return null
}
return todo.ref['@ref'].split('/').pop()
}
export default App