Last active
November 1, 2018 20:04
-
-
Save PabloRegen/2852589d28f18c6883e06f8e371d6ab7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Components structure: | |
- App | |
- Title | |
- NewTaskBar | |
- UserDisplayPreference | |
- ViewBar | |
- OrderByBar | |
- SearchBar | |
- TotalTasksToDo | |
- TaskList | |
- Task | |
- ClearTasks | |
*/ | |
import React, { Component } from 'react'; | |
import Title from './Title'; | |
import NewTaskBar from './NewTaskBar'; | |
import UserDisplayPreference from './UserDisplayPreference'; | |
import TotalTasksToDo from './TotalTasksToDo'; | |
import TasksList from './TasksList'; | |
import ClearTasks from './ClearTasks'; | |
import { amountTasksToDo } from '../lib/amountTasksToDo'; | |
import { updateTasksList } from '../lib/updateTasksList'; | |
import { viewOptions, orderByOptions } from '../lib/viewAndOrderByOptions'; | |
class App extends Component { | |
state = { | |
todoList: [], | |
newTask: '', | |
dueOn: '', | |
searchValue: '', | |
viewValue: 'all', | |
orderByValue: 'newestFirst' | |
} | |
handleChange = e => { | |
const { name, value } = e.target; | |
this.setState({ | |
[name]: value | |
}); | |
} | |
handleAddNewTask = e => { | |
e.preventDefault(); | |
const { newTask, dueOn } = this.state; | |
if (newTask.trim() === '') { | |
return; | |
} | |
const newItem = { | |
id: Date.now(), | |
text: newTask.trim(), | |
postedOn: Date.now(), | |
done: false, | |
dueOn: dueOn, | |
starred: false, | |
isWritable: false, | |
// color: null, | |
// notes: null, | |
}; | |
this.setState(state => ({ | |
newTask: '', | |
dueOn: '', | |
todoList: state.todoList.concat([newItem]), | |
})); | |
} | |
handleTaskDone = taskId => { | |
this.setState(state => ({ | |
todoList: state.todoList.map(task => { | |
if (task.id === taskId) { | |
return { ...task, done: !task.done }; | |
// or return Object.assign({}, task, { done: !task.done }); | |
} else { | |
return task; | |
} | |
}) | |
})); | |
} | |
handleTaskClicked = taskId => { | |
this.setState(state => ({ | |
todoList: state.todoList.map(task => { | |
if (task.id === taskId) { | |
return { ...task, isWritable: true }; | |
} else { | |
return task; | |
} | |
}) | |
})); | |
} | |
handleTaskEdit = (e, taskId) => { | |
this.setState(state => ({ | |
todoList: state.todoList.map(task => { | |
if (task.id === taskId) { | |
return { ...task, text: e.target.value }; | |
} else { | |
return task; | |
} | |
}) | |
})); | |
} | |
handleSaveEditedTask = (e, taskId) => { | |
e.preventDefault(); | |
this.setState(state => ({ | |
todoList: state.todoList.map(task => { | |
if (task.id === taskId) { | |
return { ...task, isWritable: false }; | |
} else { | |
return task; | |
} | |
}) | |
})); | |
} | |
handleTaskStarred = taskId => { | |
this.setState(state => ({ | |
todoList: state.todoList.map(task => { | |
if (task.id === taskId) { | |
return { ...task, starred: !task.starred }; | |
} else { | |
return task; | |
} | |
}) | |
})); | |
} | |
handleTaskDelete = (e, taskId) => { | |
e.preventDefault(); | |
this.setState(state => ({ | |
todoList: state.todoList.filter(task => task.id !== taskId) | |
})); | |
} | |
handleClearCompletedTasks = () => { | |
this.setState(state => ({ | |
todoList: state.todoList.filter(task => task.done === false) | |
})); | |
} | |
handleClearAllTasks = () => { | |
this.setState({ | |
todoList: [] | |
}); | |
} | |
handleLeavePage = () => { | |
localStorage.setItem('todoList', JSON.stringify(this.state.todoList)); | |
} | |
componentDidMount() { | |
window.addEventListener('beforeunload', this.handleLeavePage); | |
this.setState({ | |
todoList: JSON.parse(localStorage.getItem('todoList')) || [] | |
}) | |
} | |
componentWillUnmount() { | |
window.removeEventListener('beforeunload', this.handleLeavePage); | |
} | |
/* update localStorage on each & every state change. Different results than componentWillUnmount() when opening on more than 1 tab at once */ | |
// componentDidUpdate() { | |
// localStorage.setItem('todoList', JSON.stringify(this.state.todoList)); | |
// } | |
render() { | |
const { todoList, newTask, dueOn, searchValue, viewValue, orderByValue } = this.state; | |
return ( | |
<div> | |
<div className='header'> | |
<Title /> | |
<NewTaskBar | |
newTask={newTask} | |
dueOn={dueOn} | |
onChange={this.handleChange} | |
onAddNewTask={this.handleAddNewTask} /> | |
<UserDisplayPreference | |
viewOptions={viewOptions} | |
orderByOptions={orderByOptions} | |
viewValue={viewValue} | |
orderByValue={orderByValue} | |
searchValue={searchValue} | |
onChange={this.handleChange} /> | |
</div> | |
<TotalTasksToDo | |
totalTasksToDo={amountTasksToDo(todoList)} /> | |
<TasksList | |
userPreferenceToDoList={updateTasksList(todoList, viewValue, orderByValue, searchValue)} | |
onTaskDone={this.handleTaskDone} | |
onTaskClicked={this.handleTaskClicked} | |
onTaskEdit={this.handleTaskEdit} | |
onSaveEditedTask={this.handleSaveEditedTask} | |
onTaskStarred={this.handleTaskStarred} | |
onTaskDelete={this.handleTaskDelete} /> | |
<ClearTasks | |
onClearCompletedTasks={this.handleClearCompletedTasks} | |
onClearAllTasks={this.handleClearAllTasks} /> | |
</div> | |
); | |
} | |
} | |
export default App; | |
//------------------------------------------------------------------------------------------------------------- | |
// Task.js component | |
import React from 'react'; | |
import moment from 'moment'; | |
const Task = props => { | |
const { task, onTaskDone, onTaskClicked, onTaskEdit, onSaveEditedTask, onTaskStarred, onTaskDelete } = props; | |
const { text, done, dueOn, starred, id, isWritable } = task; | |
const classTaskCompleted = (done ? 'taskCompleted' : ''); | |
const dueOnText = (dueOn === '' ? '' : moment(dueOn, 'YYYY-MM-DD').calendar().split(' at')[0]); | |
const classDueToday = (dueOnText === 'Today' ? 'dueToday' : ''); | |
const Editable = ({text, onChange}) => ( | |
<span> | |
<form> | |
<input | |
type='text' | |
value={text} | |
onChange={e => onTaskEdit(e, id)} /> | |
<button | |
type='button' | |
onClick={e => onSaveEditedTask(e, id)}> | |
Save | |
</button> | |
</form> | |
</span> | |
); | |
return ( | |
<li> | |
<form className='flexRow'> | |
<input | |
type='checkbox' | |
checked={done} | |
onChange={() => onTaskDone(id)} /> | |
<span | |
className={`taskFlexGrow ${classTaskCompleted}`}> | |
{!isWritable | |
? <span onClick={() => onTaskClicked(id)}>{text}</span> | |
: <Editable | |
text={text} | |
onTaskEdit={onTaskEdit} | |
onSaveEditedTask={onSaveEditedTask} /> | |
} | |
</span> | |
<span | |
className={`${classTaskCompleted} ${classDueToday}`}> | |
{dueOnText} | |
</span> | |
<input | |
className='star' | |
type='checkbox' | |
checked={starred} | |
onChange={() => onTaskStarred(id)} /> | |
<button | |
type='button' | |
onClick={e => onTaskDelete(e, id)}> | |
x | |
</button> | |
</form> | |
</li> | |
); | |
}; | |
export default Task; | |
//------------------------------------------------------------------------------------------------------------- | |
// ERRORS | |
-> Warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form> | |
-> Uncaught TypeError: Cannot read property 'value' of null @ App.js 101 `return { ...task, text: e.target.value };` | |
-> Warning: This synthetic event is reused for performance reasons. | |
If you're seeing this, you're accessing the property `target` on a released/nullified synthetic event. | |
This is set to null. If you must keep the original synthetic event around, use event.persist(). | |
See https://fb.me/react-event-pooling for more information. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment