React how to convert Class to Hooks tutorial

The Hooks are a functional component that simplify the use of React, and allow use to create some reusable component.

I have changed on my TodoList App a class to a Hooks

The React Class

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>
    );
  }
}

React Hooks

I have changed a React Class with React Hooks:

  • timer managed with useState
  • _timeoutRef with useRef
  • ComponentDidMount, ComponentWillUnmount, ComponentDidUpdate with useEffect
const TodoListElementDelete = props => {
  const [timer, setTimer] = React.useState(props.timer);
  let _timeoutRef = React.useRef();

  function onClickUndoDelete() {
    clearTimeout(_timeoutRef.current);
    setTimer(props.timer);
    props.onClickUndoDelete();
  }

  React.useEffect(() => {
    if (props.enable) {
      _timeoutRef.current = setTimeout(() => {
        if (timer > 0) {
          setTimer(timer - 1);
        } else {
          props.onEndTimerAction();
        }
      }, 1000);
    }

    return () => clearTimeout(_timeoutRef.current);
  });

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

30 Second to React – Collection of useful React snippets

Today i want to speak about this beautiful collection of React snippet that i found on github 

There are some useful function, in my opinion the most important are about the use of Hooks.

I report here the Hooks found on GitHub

Hooks

  • useFetch
  • useInterval
  • useNavigatorOnline
  • useTimeout

useFetch

A hook that implements fetch in a declarative manner.

  • Create a custom hook that takes a url and options.
  • Use the React.useState() hook to initialize the response and error state variables.
  • Use the React.useEffect() hook to anychronously call fetch() and update the state varaibles accordingly.
  • Return an object containting the response and error state variables.
const useFetch = (url, options) => {
  const [response, setResponse] = React.useState(null);
  const [error, setError] = React.useState(null);

  React.useEffect(() => {
    const fetchData = async () => {
      try {
        const res = await fetch(url, options);
        const json = await res.json();
        setResponse(json);
      } catch (error) {
        setError(error);
      }
    };
    fetchData();
  }, []);

  return { response, error };
};

useInterval

A hook that implements setInterval in a declarative manner.

  • Create a custom hook that takes a callback and a delay.
  • Use the React.useRef() hook to create a ref for the callback function.
  • Use the React.useEffect() hook to remember the latest callback.
  • Use the React.useEffect() hook to set up the interval and clean up.
const useInterval = (callback, delay) => {
  const savedCallback = React.useRef();

  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  React.useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
};

useNavigatorOnLine

A hook that returns if the client is online or offline.

  • Create a function, getOnLineStatus, that uses the NavigatorOnLine web API to get the online status of the client.
  • Use the React.useState() hook to create an appropriate state variable, status, and setter.
  • Use the React.useEffect() hook to add listeners for appropriate events, updating state, and cleanup those listeners when unmounting.
  • Finally return the status state variable.
const getOnLineStatus = () =>
  typeof navigator !== "undefined" &amp;&amp; typeof navigator.onLine === "boolean"
    ? navigator.onLine
    : true;

const useNavigatorOnLine = () => {
  const [status, setStatus] = React.useState(getOnLineStatus());

  const setOnline = () => setStatus(true);
  const setOffline = () => setStatus(false);

  React.useEffect(() => {
    window.addEventListener("online", setOnline);
    window.addEventListener("offline", setOffline);

    return () => {
      window.removeEventListener("online", setOnline);
      window.removeEventListener("offline", setOffline);
    };
  }, []);

  return status;
};

useTimeout

A hook that implements setTimeout in a declarative manner.

  • Create a custom hook that takes a callback and a delay.
  • Use the React.useRef() hook to create a ref for the callback function.
  • Use the React.useEffect() hook to remember the latest callback.
  • Use the React.useEffect() hook to set up the timeout and clean up.
const useTimeout = (callback, delay) => {
  const savedCallback = React.useRef();

  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  React.useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setTimeout(tick, delay);
      return () => clearTimeout(id);
    }
  }, [delay]);
};

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!