Skip to content

Instantly share code, notes, and snippets.

@PabloRegen
Last active November 1, 2018 20:04
Show Gist options
  • Save PabloRegen/2852589d28f18c6883e06f8e371d6ab7 to your computer and use it in GitHub Desktop.
Save PabloRegen/2852589d28f18c6883e06f8e371d6ab7 to your computer and use it in GitHub Desktop.
/*
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