Nur Rony
Polyglot Programmer, DevOps and Forever Learner

Step by Step: Creating Todo App with React Hooks and Material UI

Step by Step: Creating Todo App with React Hooks and Material UI

It comes like a surprise when Dan Abramov and Ryan Florence is presenting React Today and Tomorrow and 90% Cleaner React React Hooks is very new React. It is still in Alpha state. So things can be changed at any moment.

Whenever I want to try something new I try to build a basic Todo App. So let's see how to build a Plain old Todo App using React Hooks

React Hooks

The two hooks we’ll be working with are:

useEffect

Docs: The function passed to useEffect will run after the render is committed to the screen. Think of effects as an escape hatch from React’s purely functional world into the imperative world.
The way I’ve thought of useEffect is similar to the way I would have thought about componentDidMount & componentDidUpdate in the past.

useState

Docs: Returns a stateful value, and a function to update it.
We will use useState to keep up with state in our functional components.

Sorry for plugging Docs reference shamelessly. I am still figuring out the magic behind th hooks and doing some experiments and will write an article soon on my understanding and findings

Points to remember when using Hooks

  1. Use state within a functional React component.
  2. Do not use Hooks inside loops and conditions.
  3. Order of calling hooks matters

Setup React Project with Hooks

  1. Let's create a plain old React App first executing this following command

    $ create-react-app todo-app-with-hooks
    $ code . # using VSCode
    
  2. Open package.json and change react, react-dom version as follows

       dependencies: {
        ...
          "react": "16.7.0-alpha.0",
          "react-dom": "16.7.0-alpha.0",
          "react-scripts": "2.1.0"
        ...
       }
    
  3. Install other dependencies as follows

    $ npm install @material-ui/core @material-ui/icons --save
    

Final Directory Structure of Todo Application

/todo-app-with-hooks
├── README.md
├── package-lock.json
├── package.json
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── components
    │   └── Todo
    │       ├── AddTodo.js
    │       ├── Layout.js
    │       ├── ListTodo.js
    │       ├── Todo.js
    │       └── hooks
    │           ├── form.js
    │           ├── index.js
    │           └── todos.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── serviceWorker.js
    └── utils.js

Create App Placeholder Component

First of all let's create a placeholder component called Layout with these following codes.

import { AppBar, Paper, Toolbar, Typography } from '@material-ui/core';
import React, { memo } from 'react';

const Layout = memo(props => (
  <Paper elevation={0} style={{ padding: 0, margin: 0, backgroundColor: '#fafafa' }}>
    <AppBar color="primary" position="static" style={{ height: 64 }}>
      <Toolbar style={{ height: 64 }}>
        <Typography variant="h5" color="inherit">
          Simple React Todo App with Hooks
        </Typography>
      </Toolbar>
    </AppBar>
    {props.children}
  </Paper>
));

export default Layout;

Create AddTodo Component

Now we need to add an input to add our Todo. For this create a file called AddTodo.js in src/components with these following code

import { Button, Grid, Paper, TextField } from '@material-ui/core';
import React, { memo } from 'react';

const AddTodo = memo(props => (
  <Paper style={{ margin: 16, padding: 16 }}>
    <Grid container>
      <Grid xs={10} md={11} item style={{ paddingRight: 16 }}>
        <TextField
          placeholder="Add Todo here"
          value={props.inputValue}
          onChange={props.onInputChange}
          onKeyPress={props.onInputKeyPress}
          fullWidth
        />
      </Grid>
      <Grid xs={2} md={1} item>
        <Button fullWidth color="primary" variant="raised" onClick={props.onButtonClick}>
          Add
        </Button>
      </Grid>
    </Grid>
  </Paper>
));

export default AddTodo;

Create Todo Component

Our each Todo is an individual data that we need to show in list so before let's create the Todo component as follows

import { Checkbox, IconButton, ListItem, ListItemSecondaryAction, ListItemText } from '@material-ui/core';
import Delete from '@material-ui/icons/Delete';
import React, { memo } from 'react';

const Todo = memo(props => (
  <ListItem divider={props.divider}>
    <Checkbox onClick={props.onCheckBoxToggle} checked={props.checked} disableRipple />
    <ListItemText primary={props.text} />
    <ListItemSecondaryAction>
      <IconButton aria-label="Delete Todo" onClick={props.onButtonClick}>
        <Delete />
      </IconButton>
    </ListItemSecondaryAction>
  </ListItem>
));

export default Todo;

Create TodoList component

We need to shows our todos as a list and here we will make the list component to show our todos in a scrollable list as follows

import { List, Paper } from '@material-ui/core';
import React, { memo } from 'react';
import Todo from './Todo';

const TodoList = memo(props => (
  <>
    {props.items.length > 0 && (
      <Paper style={{ margin: 16 }}>
        <List style={{ overflow: 'scroll' }}>
          {props.items.map((todo, index) => (
            <Todo
              {...todo}
              key={`TodoItem.${index}`}
              divider={index !== props.items.length - 1}
              onButtonClick={() => props.onItemRemove(index)}
              onCheckBoxToggle={() => props.onItemCheck(index)}
            />
          ))}
        </List>
      </Paper>
    )}
  </>
));

export default TodoList;

Creating bare bone Todo App

Now we have all necessary components for our Todo App. Let's put the pieces together in App.js as follows

import React, { memo } from 'react';
import AddTodo from './components/Todo/AddTodo';
import Layout from './components/Todo/Layout';
import TodoList from './components/Todo/ListTodo';

const TodoApp = memo(props => {
  return (
    <Layout>
      <AddTodo
        inputValue={}
        onInputChange={}
        onButtonClick={}
        onInputKeyPress={}
      />
      <TodoList items={} onItemCheck={} onItemRemove={} />
    </Layout>
  );
});

export default TodoApp;

At this moment our Todo App is done visually though it has no functionality yet. Let's start the fun and add functionality through React Hooks

Adding functionality to the App

To add the functionality we use two custom hooks called useInputValue and useTodos.Hooks are basically functions so you can do whatever you do in a JS functions but with some restrictions described above when declaring Custom Hooks. Let's see how our Custom Hooks implementation looks like

For manipulating form state place this code in form.js under hooks directory

// Filename: hooks/form.js

import { useState } from 'react';

export const useInputValue = (initialValue = '') => {
  const [inputValue, setInputValue] = useState(initialValue);

  return {
    inputValue,
    changeInput: event => setInputValue(event.target.value),
    clearInput: () => setInputValue(''),
    keyInput: (event, callback) => {
      // console.log('In keyInput');
      if (event.which === 13 || event.keyCode === 13) {
        callback(inputValue);
        return true;
      }

      return false;
    }
  };
};

and for manipulating Todos state place this code in todos.js under hooks directory

// Filename: hooks/todos.js
import { useState } from 'react';

export const useTodos = (initialValue = []) => {
  const [todos, setTodos] = useState(initialValue);
  console.info('todos in hooks', todos);
  return {
    todos,
    addTodo: text => {
      // console.log('text', text);
      if (text !== '') {
        setTodos(
          todos.concat({
            text,
            checked: false
          })
        );
      }
    },
    checkTodo: index => {
      setTodos(
        todos.map((todo, idx) => {
          if (index === idx) {
            todo.checked = !todo.checked;
          }
          return todo;
        })
      );
    },
    removeTodo(id) {
      setTodos(todos.filter((todo, index) => id !== index));
    }
  };
};

Now we have all our logic in place. Replace the code in App.js as shown below

import React, { memo } from 'react';
import AddTodo from './components/Todo/AddTodo';
import { useInputValue, useTodos } from './components/Todo/hooks';
import Layout from './components/Todo/Layout';
import TodoList from './components/Todo/ListTodo';

const TodoApp = memo(props => {
  const { inputValue, changeInput, clearInput, keyInput } = useInputValue();
  const { todos, addTodo, checkTodo, removeTodo } = useTodos();

  const clearInputAndAddTodo = _ => {
    clearInput();
    addTodo(inputValue);
  };

  return (
    <Layout>
      <AddTodo
        inputValue={inputValue}
        onInputChange={changeInput}
        onButtonClick={clearInputAndAddTodo}
        onInputKeyPress={event => keyInput(event, clearInputAndAddTodo)}
      />
      <TodoList items={todos} onItemCheck={idx => checkTodo(idx)} onItemRemove={idx => removeTodo(idx)} />
    </Layout>
  );
});

export default TodoApp;

Run the server executing following command

$ yarn start # or npm start

and you should see a barebone Todo App with Material design as follows

Hope it helps you getting an idea about React Hooks. If you have any questions please leave a comment.