import React, { Component } from 'react';
interface Item {
name: string;
completed: boolean;
}
interface AppState {
items: Item[]; // existing items
newName: string; // text in the field to add a new name
}
export class HelloApp extends Component<{}, AppState> {
constructor(props: any) {
super(props);
this.state = { items: [], newName: "" };
setTimeout(() => this.refresh(), 0); // get the correct state from the server
}
// Called when the user clisk the Refresh button. Sync to server state.
refresh() {
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:4567/list");
xhr.onload = () => this.refreshFinished(xhr);
xhr.send(null);
}
// Called when we get back the new state from the server.
refreshFinished(xhr: XMLHttpRequest) {
// NOTE: in real life, we would need to consider the possibility that the
// user has updated the local state since the request started.
if (xhr.status === 200) {
const items = [];
if (xhr.responseText.length > 0) {
const lines = xhr.responseText.split('\n');
for (let i = 0; i < lines.length; i++) {
const text = lines[i].trim();
if (text.endsWith("\t(completed)")) {
items.push({name: text.substring(0, text.length-12), completed: true});
} else {
items.push({name: text, completed: false});
}
}
}
this.setState({items: items});
} else {
console.log("Error: " + xhr.statusText); // should show the user also
}
}
render() {
let items : any[] = [];
for (let i = 0; i < this.state.items.length; i++) {
if (this.state.items[i].completed) {
items.push(
);
} else {
items.push(
this.completeItem(evt, i)} />
);
}
}
return (
To-Do List
{items}
Check the item to mark it completed.
New item:
this.setNewName(evt)} />
);
}
// Called each time the text in the new item name field is changed.
setNewName(evt: any) {
this.setState({newName: evt.target.value});
}
// Called when the user clicks on the button to add the new item.
addItem(evt: any) {
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: ""});
const xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:4567/add?name=" + encodeURIComponent(name));
xhr.onload = () => this.updateFinished(xhr);
xhr.send("");
}
}
// 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(evt: any, index: number) {
const items = this.state.items.slice(0);
items[index] = {name: items[index].name, completed: true};
this.setState({items: items});
const xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:4567/completed?name=" +
encodeURIComponent(items[index].name));
xhr.onload = () => this.updateFinished(xhr);
xhr.send("");
setTimeout(() => this.refresh(), 5500); // update when it disappears
}
// Called when the request to add an item completes.
updateFinished(xhr: XMLHttpRequest) {
// NOTE: in real life, we would need to handle this error better. We should
// either try again or notify the user of the error since their updates are
// not actually be stored permanently.
if (xhr.status !== 200) {
console.log("Error: " + xhr.statusText);
}
}
}