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 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 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 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); } } // 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 } } // Called each time the text in the new item name field is changed. setNewName(evt: any) { this.setState({newName: evt.target.value}); } 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)} />

); } }