React Todo List with Undo Timer on Delete

Goodmorning, 
it’s more time that not write, i spent some time to study ReactJS, so i think that Angular is better, but so ReactJS has its “why”.
Today i want to show how create a simple React TodoList Example in which i have implemented a system to delete an item, but whit a “undo” (possibilities of recovery the deleted item), the undo has a timer (like gmail) to recover the deleted item.

So Let’s start!

Structure of our Component

We must to think as “React”, so we define our component composition:
– TodoList
— TodoListForm
— TodoListElement
— TodoListElementDelete

At first we must to create the class, and define it in src/components/TodoList.js

Attach all the code below, you can find the entire example on CodeSandBox

import React from "react";
import Header from "./Header";

class TodoListElementDelete extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      index: 0,
      timer: 0,
      _timeoutRef: null
    };

    this.onClickUndoDelete = this.onClickUndoDelete.bind(this);
  }

  componentDidMount() {
    this.setState({
      index: this.props.index,
      timer: this.props.timer
    });
  }

  componentDidUpdate() {
    if (this.props.enable) {
      this.state._timeoutRef = setTimeout(() => {
        if (this.state.timer > 0) {
          this.setState({
            timer: this.state.timer - 1
          });
        } else {
          this.props.onEndTimerAction();
        }
      }, 1000);
    }
  }

  componentWillUnmount() {
    console.log("componentWillUnmount");
    clearTimeout(this.state._timeoutRef);
  }

  onClickUndoDelete() {
    clearTimeout(this.state._timeoutRef);
    this.setState({
      timer: this.props.timer
    });
    this.props.onClickUndoDelete();
  }

  render() {
    let classHideItem = this.props.enable ? "" : "hidden";
    return (
      <div className={"alert alert-danger " + classHideItem}>
        You want to undo the action? {this.state.timer}
        <button
          type="button"
          className="btn btn-warning"
          onClick={this.onClickUndoDelete}
        >
          Undo
        </button>
      </div>
    );
  }
}

class TodoListElement extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isDeleted: this.props.item.isDeleted
    };
    this.onClickDelete = this.onClickDelete.bind(this);
    this.onClickUndoDelete = this.onClickUndoDelete.bind(this);
    this.onClickPermanentDelete = this.onClickPermanentDelete.bind(this);
    this.onClickComplete = this.onClickComplete.bind(this);
  }

  onClickDelete() {
    let index = parseInt(this.props.item.index);
    this.props.removeItem(index);
  }

  onClickUndoDelete() {
    let item = this.props.item;
    this.props.undoRemoveItem(item);
  }

  onClickPermanentDelete() {
    this.setState({
      isDeleted: true
    });
    //this.props.definitiveRemoveItem(index);
  }

  onClickComplete() {
    let index = parseInt(this.props.item.index);
    this.props.completeItem(index);
  }

  render() {
    let isCompletedClass = this.props.item.isCompleted
      ? "list-group-item-success"
      : "";

    const btnActionComplete = this.props.item.isCompleted ? (
      ""
    ) : (
      <button
        type="button"
        className="btn btn-success btn-sm"
        onClick={this.onClickComplete}
      >
        Complete
      </button>
    );

    const btnActionDelete = (
      <button
        type="button"
        className="btn btn-danger btn-sm"
        onClick={this.onClickDelete}
      >
        Delete
      </button>
    );

    isCompletedClass = this.state.isDeleted ? "hidden" : isCompletedClass;

    return (
      <li className={"list-group-item " + isCompletedClass}>
        <div className="clarfix">
          {this.props.item.value}
          <span className="float-right">
            {btnActionComplete}
            {btnActionDelete}
          </span>
        </div>
        <TodoListElementDelete
          index={this.props.item.index}
          timer={this.props.timer}
          enable={this.props.item.isBeingDeleted}
          onClickUndoDelete={this.onClickUndoDelete}
          onEndTimerAction={this.onClickPermanentDelete}
        />
      </li>
    );
  }
}

class TodoListForm extends React.Component {
  constructor(props) {
    super(props);
    this.onSubmit = this.onSubmit.bind(this);
  }

  onSubmit(event) {
    event.preventDefault();
    var newItemValue = this.refs.itemName.value;

    if (newItemValue) {
      this.props.addItem({ newItemValue });
      this.refs.form.reset();
    }
  }

  render() {
    return (
      <form ref="form" onSubmit={this.onSubmit} className="form-inline">
        <div className="input-group mb-3">
          <input
            type="text"
            className="form-control"
            ref="itemName"
            placeholder="Write todo item"
          />
          <div className="input-group-append">
            <button className="btn btn-success" type="submit">
              Add
            </button>
          </div>
        </div>
      </form>
    );
  }
}

export default class TodoList extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      _lastIndex: 0,
      todoItems: [],
      undoRemoveItems: [],
      timer: 10
    };

    this.addItem = this.addItem.bind(this);
    this.removeItem = this.removeItem.bind(this);
    this.undoRemoveItem = this.undoRemoveItem.bind(this);
    this.definitiveRemoveItem = this.definitiveRemoveItem.bind(this);
    this.completeItem = this.completeItem.bind(this);
  }

  getNewIndex() {
    return this.state._lastIndex + 1;
  }

  addItem(todoItem) {
    const newTodoItem = {
      index: this.getNewIndex(),
      value: todoItem.newItemValue,
      isBeingDeleted: false,
      isDeleted: false,
      isCompleted: false
    };

    this.setState({
      todoItems: [...this.state.todoItems, newTodoItem],
      _lastIndex: newTodoItem.index
    });
  }

  removeItem(itemIndex) {
    let itemToBeDeleted = this.state.todoItems.find(x => x.index === itemIndex);
    if (itemToBeDeleted) {
      itemToBeDeleted.isBeingDeleted = true;
    }
    this.setState({ todoItems: this.state.todoItems });
  }

  definitiveRemoveItem(itemIndex) {
    console.log("definitiveRemoveItem", itemIndex);

    let todoItems = this.state.todoItems;
    let undoTodoItem = this.state.undoRemoveItems;
    let itemToBeDeleted = todoItems.find(
      x => x.index === itemIndex &amp;&amp; x.isBeingDeleted
    );
    let itemInUndo = undoTodoItem.find(x => x.index === itemIndex);
    if (itemToBeDeleted &amp;&amp; itemInUndo == null) {
      todoItems = todoItems.filter(x => x.index !== itemIndex);
    } else {
      undoTodoItem = undoTodoItem.filter(x => x.index !== itemIndex);
    }
    this.setState({
      todoItems: todoItems,
      undoRemoveItems: undoTodoItem
    });
  }

  undoRemoveItem(item) {
    let todoItems = this.state.todoItems;
    let itemToBeDeleted = todoItems.find(x => x.index === item.index);
    itemToBeDeleted.isBeingDeleted = false;
    this.setState({
      todoItems: todoItems
    });
  }

  completeItem(itemIndex) {
    let todoItems = this.state.todoItems;
    let itemComplete = todoItems.find(x => x.index === itemIndex);
    itemComplete.isCompleted = true;
    this.setState({
      todoItems: todoItems
    });
  }

  render() {
    let items = this.state.todoItems.map((item, index) => {
      return (
        <TodoListElement
          key={index}
          item={item}
          timer={this.state.timer}
          completeItem={this.completeItem}
          removeItem={this.removeItem}
          undoRemoveItem={this.undoRemoveItem}
          definitiveRemoveItem={this.definitiveRemoveItem}
        />
      );
    });

    return (
      <div>
        <Header />
        <div className="container">
          <TodoListForm addItem={this.addItem} />
          <ul className="list-group"> {items} </ul>
        </div>
      </div>
    );
  }
}

Do you have some suggestion about React programming? Thank you!

Nodemcu Light Control using Nodejs SSDP

LightControl con Nodejs SSDP

questo pacchetto ha una parte di script Arduino da poter lanciare su tutte le vostre NodeMcu e una parte applicativa realizzata con nodejs che gestisce le periferiche ed interagisce con loro utilizzando il protocollo SSDP che trovo veramente interessante per l’aspetto iot, evita di utilizzare/conoscere gli ip delle schede, ogni device connessione in rete espone una propria scheda identificativa, tramite le quale potete riconoscerlo e quindi interagirci:

SSDP.setSchemaURL("description.xml"); 
SSDP.setHTTPPort(80); 
SSDP.setName("NodemcuLightOne"); 
SSDP.setSerialNumber("0001"); 
SSDP.setURL("index.html"); 
SSDP.setModelName("nodemcu"); 
SSDP.setModelNumber("0001"); 
SSDP.setDeviceType("upnp:nodemculight"); 
SSDP.begin();

Il client nodejs è in ascolto sulla porta 3000.
Il device nodemcu viene interrogato sulla porta 80.

##NodeMCU
per configurarlo bisogna prima scaricare i driver https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers e poi, per comodita’, abbiamo utilizzato l’ide di Arduino, debitamente onfigurato così come indicato nelle numerose guide on line. Per installare il mio script su nodemcu ho utilizzato Arduino IDE, se avete bisogno di maggiori informazioni contattatemi pure.

##Node.js
va installato su una macchina e messo in ascolto sulla porta 3000, così come indicato nel pacchetto. 

##INSTALLAZIONE per avviare il server bisogna procedere nel seguente modo:

  1. Scaricare e installare la versione idonea di node ed npm: https://nodejs.org/it/download/
  2. installare il framework express tramite il gestore dei pacchetti: npm intall express

è possibile anche configurare tutto tramite npm install, a patto che il file package.json sia aggiornato nelle dipendenze necessarie. a questo punto si potrà avviare il server:

node app.js

Da notare che su sistemi Debian Node viene avviato con nodejs app.js, in quanto node è un altro applicativo presente sui repository.

 

Come configurare NodeJs SSDP

SSDP la cui definizione è Simple Service Discovery Protocol, viene utilizzato per la ricerca dei device connessi nella rete locale, ho utilizzato la libreria node-ssdp-js semplice da installare e da utilizzare. Bisogna definire una firma nei nostri device da poter successivamente “cercare”.

#JAVASCRIPT NODEJS

var Client = require('node-ssdp-js').Client, client = new Client();

client.on('response', function (response) {
    console.log('Response ssdp device:', response);
    var ipNodeMcuLight = response.referrer.address;
    console.log('IP:', ipNodeMcuLight);
});

client.browse('upnp:nodemculight');

#ARDUINO SCRIPT

SSDP.setDeviceType("upnp:nodemculight");

Progetto “LightControl” di Francesco Pantisano realizzato utilizzando come progetto di base “Plant” di Diomede Mazzone con la collaborazione di Fabio Z Tessitore (@FabioZTessitore) che ringrazio per la pazienza avuta a rispondere alle mie domande.

Tag: NodeJS, ExpressJS, HTML 5, client/server, Simple Service Discovery Protocol (SSDP)

Licenza: GPL 3.0 o successive