Saltar al contenido principal
Tecnología

Creando una hermosa Todo App usando React Hooks + Material UI

4 min de lectura
Creando una hermosa Todo App usando React Hooks + Material UI

Como empezar?

Antes que nada, vamos a empezar creando un nuevo CodeSandbox.

Vamos a descomponer la Todo App en 4 componentes:

  • Layout
  • AddTodo
  • TodoListItem
  • TodoList

Como no queremos que todos estos componentes se re-rendericen a menos que sea realmente necesario, los vamos a envolver usando el helper memo de React.

Desarrollemos el Layout

Nuestro componente Layout solo va a renderizar un Toolbar con el nombre de nuestra App y va a poder recibir componentes hijos.

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

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 color="inherit">TODO APP</Typography>
      </Toolbar>
    </AppBar>
    {props.children}
  </Paper>
));

export default Layout;

Desarrollemos el AddTodo

Nuestro AddTodo va a renderizar un componente Paper envolviendo un input y un boton, ambos van a recibir event handlers pasados como props desde un componente padre.

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

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="secondary"
          variant="outlined"
          onClick={props.onButtonClick}
        >
          Add
        </Button>
      </Grid>
    </Grid>
  </Paper>
));

export default AddTodo;

Desarrollemos el TodoListItem

Nuestro componente TodoListItem va a renderizar un ListItem envolviendo un checkbox, un contenedor de texto y un boton como elementos hijos.

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

const TodoListItem = 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}>
        <DeleteOutlined />
      </IconButton>
    </ListItemSecondaryAction>
  </ListItem>
));

export default TodoListItem;

Desarrollemos el TodoList

Nuestro componente TodoList va a renderizar solo cuando haya un item agregado a la lista. Va a estar a cargo de renderizar los componentes TodoListItem.

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

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

export default TodoList;

Pasemos a la logica

Bueno, loco, ya tenemos todos los componentes listos. Es momento de pensar en la logica y aca vamos a hacer toda la logica usando custom React Hooks (Hell yeah!).

Vamos a descomponer la logica de la Todo App en 2 variantes:

  • Manejo de estado del input
  • Manejo de estado de los todos

Desarrollemos la logica del estado del Input

El estado del input va a tener un unico valor que va a ser un string, nuestro custom hook va a retornar ese valor, y tambien, un conjunto de funciones para manejar los eventos de cambio, reset e input.

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) => {
      if (event.which === 13 || event.keyCode === 13) {
        callback(inputValue);
        return true;
      }

      return false;
    },
  };
};

Desarrollemos la logica del estado de los Todos

El estado de los todos va a tener un unico valor que va a ser un array, nuestro custom hook va a retornar ese valor, y tambien, un conjunto de funciones custom para manejar el agregado, el checkeo y la eliminacion de todos.

import { useState } from 'react';

export const useTodos = (initialValue = []) => {
  const [todos, setTodos] = useState(initialValue);

  return {
    todos,
    addTodo: text => {
      if (text !== '') {
        setTodos(
          todos.concat({
            text,
            checked: false,
          })
        );
      }
    },
    checkTodo: idx => {
      setTodos(
        todos.map((todo, index) => {
          if (idx === index) {
            todo.checked = !todo.checked;
          }

          return todo;
        })
      );
    },
    removeTodo: idx => {
      setTodos(todos.filter((todo, index) => idx !== index));
    },
  };
};

Conectando todas las piezas

Tenemos los componentes, y tambien la logica. Mezclemos todo para hacer que nuestra App funcione:

import './styles.css';
import React, { memo } from 'react';
import ReactDOM from 'react-dom';

import { useInputValue, useTodos } from './custom-hooks';
import Layout from './components/Layout';
import AddTodo from './components/AddTodo';
import TodoList from './components/TodoList';

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

ReactDOM.render(<TodoApp />, document.getElementById('root'));

Miremos los resultados

Veamos nuestra Todo App andando:

Etiquetas

React Hooks Frontend