todoapp.ts
import React, { Component, MouseEvent, ChangeEvent } from 'react';
type Item = {
name: string;
completed: boolean;
};
interface TodoState {
items: Item[]; // existing items
newName: string; // text in the field to add a new name
}
export class ToDoApp extends Component<{}, TodoState> {
constructor(props: {}) {
super(props);
this.state = { items: [], newName: "" };
}
componentDidMount() {
this.refresh(); // get the real data from the server
}
// Called to fresh our list of data from the server.
refresh = () => {
fetch("/api/list")
.then(this.handleRefresh)
.catch(this.handleServerError);
};
// Called with the response from a request to /list
handleRefresh = (res: Response) => {
if (res.status === 200) {
res.json().then(this.handleRefreshText)
.catch(this.handleServerError);
} else {
this.handleServerError(res);
}
};
// Called with the text of the response from /list
handleRefreshText = (vals: unknown) => {
if (!Array.isArray(vals)) {
console.error("bad data from /list", vals)
return;
}
const items: Item[] = [];
for (const val of vals) {
if ('name' in val && typeof val.name === 'string' &&
'completed' in val && typeof val.completed === 'boolean') {
items.push({name: val.name, completed: val.completed});
} else {
console.error('bad item in list', val)
}
}
this.setState({items: items});
};
// Called when the user checks the box next to an uncompleted item. The
// second parameter is the index of that item in the list.
completeItem = (_: ChangeEvent<HTMLInputElement>, index: number): void => {
const items = this.state.items.slice(0);
items[index] = {name: items[index].name, completed: true};
this.setState({items: items});
fetch("/api/complete?name=" + encodeURIComponent(items[index].name),
{method: "POST"})
.then(this.handleCompleted)
.catch(this.handleServerError)
}
// Called when the server confirms that the item was completed.
handleCompleted = (res: Response) => {
if (res.status === 200) {
setTimeout(this.refresh, 5500); // should be gone then
} else {
this.handleServerError(res);
}
};
// Called when the user clicks on the button to add the new item.
addItem = (_: MouseEvent<HTMLButtonElement>): void => {
const name = this.state.newName.trim().replace('\t', ' '); // no tabs
if (name.length > 0) {
const items = this.state.items.slice(0);
items.push({name: name, completed: false});
this.setState({items: items, newName: ""});
fetch("/api/add?name=" + encodeURIComponent(name),
{method: "POST"})
.then(this.handleAdded)
.catch(this.handleServerError)
}
}
// Called when the server confirms that the item was added.
handleAdded = (res: Response) => {
if (res.status !== 200) {
this.handleServerError(res);
}
};
// Called each time the text in the new item name field is changed.
setNewName = (evt: ChangeEvent<HTMLInputElement>): void => {
this.setState({newName: evt.target.value});
}
render = (): JSX.Element => {
let items : JSX.Element[] = [];
for (let i = 0; i < this.state.items.length; i++) {
if (this.state.items[i].completed) {
items.push(
<div className="form-check" key={i}>
<input className="form-check-input" type="checkbox"
id={"check" + i} checked={true} readOnly={true} />
<label className="form-check-label completed" htmlFor={"check" + i}>
{this.state.items[i].name}
</label>
</div>);
} else {
items.push(
<div className="form-check" key={i}>
<input className="form-check-input" type="checkbox"
id={"check" + i} checked={false}
onChange={evt => this.completeItem(evt, i)} />
<label className="form-check-label" htmlFor={"check" + i}>
{this.state.items[i].name}
</label>
</div>);
}
}
return (
<div>
<h2>To-Do List</h2>
{items}
<p className="instructions">Check the item to mark it completed.</p>
<p className="more-instructions">New item:
<input type="text" className="new-item"
value={this.state.newName}
onChange={this.setNewName} />
<button type="button" className="btn btn-link"
onClick={this.addItem}>Add</button>
</p>
</div>);
}
// Called when we fail to communicate correctly with the server.
handleServerError = (_: Response) => {
// TODO: show the error to the user, with more information
console.error(`unknown error talking to server`);
};
}
Full Code