"Первая часть"

Создание компонентов и отправка действий

В папке src создайте папку компонентов. Внутри этой папки создайте несколько компонентов.

Заголовок.tsx

import React, { FC } from 'react';
interface HeaderProps {
  title: string;
  subtitle?: string;
}
const Header: FC<HeaderProps> = ({ title, subtitle }) => {
  return (
    <header className="hero has-text-centered is-dark is-bold mb-5">
      <div className="hero-body">
        <div className="container">
          <h1 className="title mb-3">{title}</h1>
          <h2 className="subtitle mt-0">{subtitle}</h2>
        </div>
      </div>
    </header>
  );
}
export default Header;

Заголовок — это функциональный компонент, поэтому мы можем использовать тип FC. У него есть некоторые свойства, поэтому мы можем создать интерфейс HeaderProps. У него будет заголовок, который является строкой, и подзаголовок, который также является строкой, но это необязательно, поэтому я использую ? после имени свойства.

Заголовок — очень простой компонент, он выводит главный компонент с заголовком и подзаголовком.

Notification.tsx

import React, { FC, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setNotification } from '../store/actions';
import { RootState } from '../store/store';
interface NotificationProps {
  msg: string;
}
let timeout: ReturnType<typeof setTimeout>;
const Notification: FC<NotificationProps> = ({ msg }) => {
  const dispatch = useDispatch();
  const type = useSelector((state: RootState) => state.notification.type);
  useEffect(() => {
    if(msg !== '') {
      if(timeout){
        clearTimeout(timeout);
      }
      timeout = setTimeout(() => {
        dispatch(setNotification(''));
      }, 3000);
    }
  }, [dispatch, msg]);
  const closeNotification = () => {
    dispatch(setNotification(''));
    clearTimeout(timeout);
  }
  return (
    <div className={msg ? `${type === 'danger' ? 'notification is-danger' : 'notification is-primary'}` : 'notification is-hidden'}>
      <button className="delete" onClick={closeNotification}></button>
      <p>{msg}</p>
    </div>
  );
}
export default Notification;

Компонент уведомлений является функциональным компонентом, поэтому нам нужно добавить тип FC, и он будет иметь поддержку msg, поэтому мы создадим интерфейс NotificationsProps и добавим свойство msg, а тип этого свойства — строка. Нам также нужен RootState, потому что нам нужно получить тип из состояния уведомления.

Уведомление закроется через 3 секунды, и для этого мы будем использовать метод setTimeout, поэтому нам нужна переменная тайм-аута типа ReturnType<typeof setTimeout>.

В хуке useEffect мы можем проверить, не является ли сообщение пустым, и если это правда, нам нужно очистить время ожидания, если установлено время ожидания, и вызвать setTimeout и отправить действие setNotification с пустой строкой в ​​​​качестве параметра для сброса сообщения, и если сообщение пустое, уведомление не будет быть видимым.

У уведомления есть кнопка удаления, и когда эта кнопка нажата, мы можем отправить действие setNotification с пустой строкой в ​​качестве параметра и очистить время ожидания, чтобы скрыть уведомление.

Создать новый список.tsx

import React, { FC, FormEvent, useState } from 'react';
import { useDispatch } from 'react-redux';
import { addList, setNotification } from '../store/actions';
import { List } from '../store/types';
const CreateNewList: FC = () => {
  const dispatch = useDispatch();
  const [listName, setListName] = useState<string>('');
  const inputChangeHandler = (e: FormEvent<HTMLInputElement>) => {
    setListName(e.currentTarget.value);
  }
  const submitHandler = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if(listName === '') {
      return alert('List name is required!');
    }
    const newList: List = {
      id: `list-${new Date().getTime()}`,
      name: listName,
      tasks: []
    };
    dispatch(addList(newList));
    dispatch(setNotification(`New list("${newList.name}") created`));
    setListName('');
  }
  return (
    <div className="card mb-5">
      <div className="card-header">
        <p className="card-header-title">Create New List</p>
      </div>
      <div className="card-content">
        <form onSubmit={submitHandler}>
          <div className="field">
            <label className="label">List Name</label>
            <div className="control">
              <input 
                className="input" 
                type="text" 
                placeholder="List Name" 
                name="listname" 
                value={listName} 
                onChange={inputChangeHandler} 
              />
            </div>
          </div>
          <div className="control">
            <button type="submit" className="button is-primary">Create</button>
          </div>
        </form>
      </div>
    </div>
  );
}
export default CreateNewList;

Этот компонент будет использоваться для добавления нового списка и сохранения его в локальном хранилище. Это тоже функциональный компонент. Поскольку компонент имеет форму, мы можем импортировать интерфейс FormEvent из реакции и использовать состояние для управления состоянием поля ввода.

Мы также отправим некоторые действия, поэтому нам нужно использовать хук useDispatch из пакета react-redux. Мы будем использовать действия addList и setNotification, поэтому их также необходимо импортировать. И нам также нужен тип списка из нашего файла типов.

Этот компонент будет выводить простую форму, и когда форма будет отправлена, мы можем выполнить простую проверку, и если проверка не завершится ошибкой, мы можем создать новый список и отправить действие addList и действие setNotification и сбросить значение поля ввода.

Списки.tsx

import React, { FC, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getLists, setListIdToDelete, setListToEdit } from '../store/actions';
import { RootState } from '../store/store';
import { List } from '../store/types';
const Lists: FC = () => {
  const dispatch = useDispatch();
  const lists = useSelector((state: RootState) => state.list.lists);
  useEffect(() => {
    dispatch(getLists());
  }, [dispatch]);
  const setListIdToDeleteHandler = (id: string) => {
    dispatch(setListIdToDelete(id));
  }
  const setListToEditHandler = (id: string) => {
    dispatch(setListToEdit(id));
  }
  return (
    <div className="panel is-primary">
      <p className="panel-heading">Your lists</p>
      <div id="lists-wrapper">
        { Object.keys(lists).length === 0 
          ?
            <p id="no-lists" className="py-4 has-text-centered">No Lists</p>
          :
            <div id="task-lists">
              {Object.values(lists).map((list: List) => {
                return <div className="panel-block py-3" key={list.id}>
                  <p onClick={() => setListToEditHandler(list.id)}>{list.name}</p>
                  <span className="panel-icon has-text-danger" onClick={() => setListIdToDeleteHandler(list.id)}>
                    <i className="fas fa-times-circle"></i>
                  </span>
                </div>
              })}
            </div>
        }
      </div>
    </div>
  );
}
export default Lists;

Этот компонент выведет список всех наших списков из локального хранилища. Сначала мы будем использовать хук useSelector для получения списков из нашего состояния списка, как вы можете видеть, здесь нам нужно использовать тип RooState, таким образом typescript знает, что мы хотим получить списки из нашего редюсера списка.

Когда компонент смонтирован, мы будем использовать хук useEffect для отправки действия getLists, чтобы получить все списки из локального хранилища. Если списков нет, выведем текст No lists, в противном случае каждый список будет выведен в виде панели-блока bulma.

Когда мы щелкаем имя списка, мы хотим установить список, который мы хотим отредактировать, в свойство listToEdit, поэтому мы отправим действие setListToEdit. И когда мы нажимаем кнопку удаления, мы хотим установить идентификатор списка, который мы хотим удалить, в свойство listIdToDelete, поэтому нам нужно отправить действие setListIdToDelete.

EditListModal.tsx

import React, { FC, useState, FormEvent } from 'react';
import { useDispatch } from 'react-redux';
import { setListToEdit, updateList, setNotification } from '../store/actions';
import { List } from '../store/types';
interface EditListModalProps {
  list: List;
}
const EditListModal: FC<EditListModalProps> = ({ list }) => {
  const dispatch = useDispatch();
  const [listName, setListName] = useState(list.name);
  const hideModalHandler = () => {
    dispatch(setListToEdit(''));
  }
  const changeHandler = (e: FormEvent<HTMLInputElement>) => {
    setListName(e.currentTarget.value);
  }
  const submitHandler = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if(listName.trim() === '') {
      return alert('List name is required!');
    }
    if(listName.trim() === list.name) {
      return alert('List name is the same as before!');
    }
    dispatch(updateList(list.id, listName.trim()));
    dispatch(setNotification(`List "${list.name}" updated!`));
  }
  return (
    <div className="modal is-active">
      <div className="modal-background close-modal" onClick={hideModalHandler}></div>
      <form className="modal-card" onSubmit={submitHandler}>
        <header className="modal-card-head">
          <p className="modal-card-title">Edit List</p>
          <button type="button" className="delete close-modal" aria-label="close" onClick={hideModalHandler}></button>
        </header>
        <div className="modal-card-body">
          <div className="field">
            <label className="label">List Name</label>
            <div className="control">
              <input type="text" className="input" name="listname" placeholder="List Name" value={listName} onChange={changeHandler} />
            </div>
          </div>
        </div>
        <footer className="modal-card-foot">
          <button type="submit" className="button is-success">Save changes</button>
          <button type="button" className="button close-modal" onClick={hideModalHandler}>Cancel</button>
        </footer>
      </form>
    </div>
  );
}
export default EditListModal;

Этот компонент будет использоваться для обновления/редактирования имени списка.

Поскольку у него будет свойство списка, мы можем создать интерфейс для реквизита со свойством списка типа List. С помощью свойства списка мы можем использовать хук useState и установить значение по умолчанию для имени списка, и мы можем использовать его в нашем поле ввода, и когда модальное окно откроется, имя списка будет добавлено в поле ввода.

Когда форма отправлена, мы можем выполнить некоторую проверку, и если проверка пройдена, мы можем отправить действия updateList и setNotification.

И когда мы хотим закрыть модальное окно, мы можем просто отправить действие setListToEdit с пустой строкой, потому что в файле App.tsx мы проверим, установлен ли listToEdit, и только в этом случае мы покажем модальное окно, и если оно пустое, модальное окно закроется.

УдалитьListModal.tsx

import React, { FC, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { setListIdToDelete, deleteList, getListById, setNotification } from '../store/actions';
import { RootState } from '../store/store';
interface DeleteListModalProps {
  listId: string;
}
const DeleteListModal: FC<DeleteListModalProps> = ({ listId }) => {
  const dispatch = useDispatch();
  const list = useSelector((state: RootState) => state.list.listById);
  useEffect(() => {
    dispatch(getListById(listId));
  }, [dispatch, listId]);
  const deleteListHandler = () => {
    dispatch(deleteList(listId));
    if(list) {
      dispatch(setNotification(`List "${list.name}" deleted!`, 'danger'));
    }
  }
  const hideModalHandler = () => {
    dispatch(setListIdToDelete(''));
  }
  return (
    <div className="modal is-active">
      <div className="modal-background close-modal" onClick={hideModalHandler}></div>
      <div className="modal-card">
        <header className="modal-card-head has-text-centered">
          <p className="modal-card-title">Are you sure you want to delete this list ?</p>
        </header>
        <div className="modal-card-body">
          <h2 className="is-size-5 has-text-centered">All tasks related to this list will be deleted</h2>
          <div className="content">
            {list?.tasks.length === 0 ?
              <p className="has-text-centered pt-4 mb-0">No tasks in this list!</p>
              :
              <ul>
                {list?.tasks.map(task => (
                  <li key={task.id}>{task.name}</li>
                ))}
              </ul>
            }
          </div>
        </div>
        <footer className="modal-card-foot">
          <button type="submit" className="button is-danger" onClick={deleteListHandler}>Delete</button>
          <button type="button" className="button close-modal" onClick={hideModalHandler}>Cancel</button>
        </footer>
      </div>
    </div>
  );
}
export default DeleteListModal;

Компонент DeleteListModal удалит список при нажатии кнопки удаления или закроет модальное окно при нажатии кнопки отмены или модального фона. Если в списке есть задачи, они также будут отображаться в этом компоненте только для того, чтобы предупредить пользователя о том, что они будут удалены при удалении списка.

Когда компонент смонтирован, мы будем использовать хук useEffect, чтобы получить список по идентификатору, который мы передали как свойство.

Когда нажата кнопка удаления, мы отправим действие deleteList и действие setNotification с установленным типом опасности. И когда нажимается кнопка отмены или модальный фон, мы отправим действие setListIdToDelete, чтобы установить listIdToDelete из состояния списка в пустую строку, чтобы закрыть модальное окно.

Боковая панель.tsx

import React, { FC } from 'react';
import CreateNewList from './CreateNewList';
import Lists from './Lists';
const Sidebar: FC = () => {
  return(
    <div className="column is-3">
      <CreateNewList />
      <Lists />
    </div>
  );
}
export default Sidebar;

В этом компоненте мы просто выведем компоненты CreateNewList и Lists, а затем импортируем их в компонент приложения.

Пока мы можем добавлять, редактировать и удалять списки. Мы можем проверить это сейчас. Нам нужно обновить файл App.tsx.

import React from 'react';
import { useSelector } from 'react-redux';
import './App.css';
import Header from './components/Header';
import Sidebar from './components/Sidebar';
import DeleteListModal from './components/DeleteListModal';
import EditListModal from './components/EditListModal';
import Notification from './components/Notification';
import { RootState } from './store/store';
function App() {
  const listIdToDelete = useSelector((state: RootState) => state.list.listIdToDelete);
  const listToEdit = useSelector((state: RootState) => state.list.listToEdit);
  const notificationMsg = useSelector((state: RootState) => state.notification.message);
  return (
    <div className="App">
      <Header title="Task List App" subtitle="Create some lists and add some tasks to each list" />
      <div className="container px-5">
        <div className="columns">
          <Sidebar />
        </div>
      </div>
      <Notification msg={notificationMsg} />
      {listIdToDelete && <DeleteListModal listId={listIdToDelete} /> }
      {listToEdit && <EditListModal list={listToEdit} />}
    </div>
  );
}
export default App;

Если вы добавляете новый список, этот список необходимо сохранить в локальном хранилище, добавить в раздел «Ваши списки» и выбрать поле в качестве опции. Вы также увидите уведомление, когда список будет добавлен. И если вы нажмете на этот список, откроется модальное окно редактирования списка, и там вы сможете обновить имя списка. И если вы нажмете кнопку «Удалить» рядом с именем списка, откроется модальное окно удаления списка, и здесь вы можете нажать кнопку «Удалить», чтобы удалить список и удалить его из локального хранилища.

Теперь давайте добавим несколько задач в наши списки.

SelectList.tsx

import React, { FC, FormEvent } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { List } from '../store/types';
import { setSelectedList } from '../store/actions';
import { RootState } from '../store/store';
const SelectList: FC = () => {
  const dispatch = useDispatch();
  const lists = useSelector((state: RootState) => state.list.lists);
  const selectChangeHandler = (e: FormEvent<HTMLSelectElement>) => {
    dispatch(setSelectedList(e.currentTarget.value));
  }
  return (
    <section>
      <h2 className="is-size-4 has-text-centered mb-4">Choose a list</h2>
      <div className="field mb-5">
        <div className="control has-icons-left">
          <div className="select fullwidth">
            <select className="fullwidth" onChange={selectChangeHandler}>
              <option value="">Select List</option>
              {Object.keys(lists).length > 0 && 
                Object.values(lists).map((list: List) => (
                  <option key={list.id} value={list.id}>{list.name}</option>
                ))
              }
            </select>
          </div>
          <div className="icon is-small is-left">
            <i className="fas fa-list"></i>
          </div>
        </div>
      </div>
    </section>
  );
}
export default SelectList;

Этот компонент имеет поле выбора, заполненное всеми созданными вами списками, и когда список выбран, действие setSelectedList будет отправлено, и выбранный список из состояния списка будет установлен в этот список. Если выбран список, компоненты AddNewTask и Tasks больше не будут скрыты.

AddNewTask.tsx

import React, { FC, useState, FormEvent } from 'react';
import { useDispatch } from 'react-redux';
import { List, Task } from '../store/types';
import { addTask, setNotification } from '../store/actions';
interface AddNewTaskProps {
  list: List;
}
const AddNewTask: FC<AddNewTaskProps> = ({ list }) => {
  const dispatch = useDispatch();
  const [taskName, setTaskName] = useState('');
  const inputChangeHandler = (e: FormEvent<HTMLInputElement>) => {
    setTaskName(e.currentTarget.value);
  }
  const submitHandler = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if(taskName === '') {
      return alert('Task name is required!');
    }
    const newTask: Task = {
      name: taskName,
      id: `task-${new Date().getTime()}`,
      completed: false
    }
    dispatch(addTask(newTask, list));
    dispatch(setNotification(`New task created("${newTask.name}")!`));
    setTaskName('');
  }
  return (
    <section className="section">
      <h2 className="is-size-4 has-text-centered">Add new task to selected list</h2>
      <form id="task-form" onSubmit={submitHandler}>
        <div className="field">
          <label className="label">Task Name</label>
          <div className="control">
            <input type="text" className="input" placeholder="Add Task" value={taskName} onChange={inputChangeHandler} />
          </div>
          <div className="control mt-4">
            <input type="submit" value="Add New Task" className="button is-primary" />
          </div>
        </div>
      </form>
    </section>
  );
}
export default AddNewTask;

Этот компонент выводит простую форму для добавления новой задачи в выбранный список.

При отправке формы будет создана новая задача и будут отправлены действия addTask и setNotification.

Tasks.tsx

import React, { FC } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Task } from '../store/types';
import { setTaskToDelete, setTaskToEdit } from '../store/actions';
import { RootState } from '../store/store';
interface TasksProps {
  tasks: Task[];
}
const Tasks: FC<TasksProps> = ({ tasks }) => {
  const dispatch = useDispatch();
  const list = useSelector((state: RootState) => state.list.selectedList!);
  const setTaskToDeleteHandler = (task: Task) => {
    dispatch(setTaskToDelete(task, list));
  }
  const setTaskToEditHandler = (task: Task) => {
    dispatch(setTaskToEdit(task, list));
  }
  const tasksTable = (
    <table id="tasks-table" className="table is-fullwidth is-striped">
      <thead>
        <tr>
          <th>Task</th>
          <th className="has-text-centered">Edit</th>
          <th className="has-text-centered">Delete</th>
        </tr>
      </thead>
      <tbody>
        {tasks.map((task: Task) => (
          <tr key={task.id} className={task.completed ? 'completed' : ''}>
            <td>{task.name}</td>
            <td className="has-text-centered">
              <button className="button is-primary is-small" onClick={() => setTaskToEditHandler(task)}>
                <span className="icon">
                  <i className="fas fa-edit"></i>
                </span>
              </button>
            </td>
            <td className="has-text-centered">
              <button className="button is-danger is-small" onClick={() => setTaskToDeleteHandler(task)}>
                <span className="icon">
                  <i className="fas fa-times"></i>
                </span>
              </button>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
  return (
    <section className="section">
      <h2 className="is-size-4 has-text-centered">List of tasks in selected list</h2>
      {tasks.length === 0 ? <p id="no-tasks" className="py-4 has-text-centered">No Tasks</p> : tasksTable}
    </section>
  );
}
export default Tasks;

Этот компонент выведет все задачи из выбранного списка или текст «Нет задач», если в выбранном списке нет задач.

Каждая задача в таблице также будет иметь кнопки редактирования и удаления. Если вы нажмете кнопку редактирования, задача TaskToEdit из списка будет установлена, и откроется модальное окно редактирования задачи. И если вы нажмете кнопку удаления, будет установлено задание TaskToDelete, и откроется модальное окно удаления задачи.

EditTaskModal.tsx

import React, { FC, FormEvent, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Task, List } from '../store/types';
import { updateTask, unsetTaskToEdit, setNotification } from '../store/actions';
interface EditTaskModalProps {
  taskToEdit: {
    task: Task;
    list: List;
  }
}
const EditTaskModal: FC<EditTaskModalProps> = ({ taskToEdit: { task, list }}) => {
  const dispatch = useDispatch();
  const [taskName, setTaskName] = useState(task.name);
  const [taskState, setTaskState] = useState(task.completed);
  const nameChangeHandler = (e: FormEvent<HTMLInputElement>) => {
    setTaskName(e.currentTarget.value);
  }
  const stateChangeHandler = (e: FormEvent<HTMLInputElement>) => {
    setTaskState(e.currentTarget.checked);
  }
  const submitHandler = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if(taskName === '') {
      return alert('Task name is required!');
    }  
    if(taskName === task.name && taskState === task.completed) {
      return alert('Task name and state are same as before!');
    }
    dispatch(updateTask(task.id, taskName, taskState, list));
    dispatch(setNotification(`Task "${task.name}" updated!`));
  }
  const closeModalHandler = () => {
    dispatch(unsetTaskToEdit());
  }
  return (
    <div className="modal is-active">
      <div className="modal-background close-modal" onClick={closeModalHandler}></div>
      <form className="modal-card" onSubmit={submitHandler}>
        <header className="modal-card-head">
          <p className="modal-card-title">Edit Task</p>
          <button type="button" className="delete modal-close" aria-label="close" onClick={closeModalHandler}></button>
        </header>
        <div className="modal-card-body">
          <div className="field">
            <label className="label">Task Name</label>
            <div className="control">
              <input type="text" className="input" name="taskname" placeholder="Task Name" value={taskName} onChange={nameChangeHandler} />
            </div>
          </div>
          <div className="field">
            <label className="label">Complete task</label>
            <label className="checkbox">
              <input type="checkbox" name="taskstate" checked={taskState} onChange={stateChangeHandler} />
              {' '}Complete
          </label>
          </div>
        </div>
        <footer className="modal-card-foot">
          <button type="submit" className="button is-success">Save changes</button>
          <button type="button" className="button close-modal" onClick={closeModalHandler}>Cancel</button>
        </footer>
      </form>
    </div>
  );
}
export default EditTaskModal;

Когда это модальное окно открыто, вы можете редактировать задачу. Вы можете изменить название задачи и завершить или отменить задачу. Когда форма отправлена, действие updateTask отправляется с 4 параметрами, идентификатором задачи, именем задачи, состоянием задачи (завершено/незавершенно) и списком, к которому принадлежит эта задача. Также будет отправлено действие setNotification. Если вы хотите закрыть модальное окно, вам просто нужно отправить действие unsetTaskToEdit, потому что, когда для него установлено значение null, модальное окно закрывается.

УдалитьTaskModal.tsx

import React, { FC } from 'react';
import { useDispatch } from 'react-redux';
import { Task, List } from '../store/types';
import { unsetTaskToDelete, deleteTask, setNotification } from '../store/actions';
interface DeleteTaskModalProps {
  taskToDelete: {
    task: Task;
    list: List;
  }
}
const DeleteTaskModal: FC<DeleteTaskModalProps> = ({ taskToDelete: { task, list } }) => {
  const dispatch = useDispatch();
  const deleteHandler = () => {
    dispatch(deleteTask(task, list));
    dispatch(setNotification(`Task "${task.name}" deleted!`, 'danger'));
  }
  const closeModalHandler = () => {
    dispatch(unsetTaskToDelete());
  }
  return (
    <div className="modal is-active">
      <div className="modal-background close-modal" onClick={closeModalHandler}></div>
      <div className="modal-card">
        <header className="modal-card-head has-text-centered">
          <p className="modal-card-title">Are you sure you want to delete this task ?</p>
        </header>
        <footer className="modal-card-foot">
          <button type="submit" className="button is-danger" onClick={deleteHandler}>Delete</button>
          <button type="button" className="button close-modal" onClick={closeModalHandler}>Cancel</button>
        </footer>
      </div>
    </div>
  );
}
export default DeleteTaskModal;

Этот компонент используется для удаления задачи. Когда нажимается кнопка удаления, отправляется действие deleteTask, нам нужно передать задачу и список в качестве аргументов, и мы можем получить их из свойств компонента (мы устанавливаем их в свойство taskToDelete, когда нажимаем кнопку удаления), и задача удаляется из списка и обновленный список сохраняется в локальном хранилище. Действие setNotification также отправляется при нажатии кнопки удаления.

И чтобы скрыть модальное окно, нам нужно отправить действие unsetTaskToDelete, которое установит для taskToDelete значение null.

MainContent.tsx

import React, { FC, Fragment } from 'react';
import { useSelector } from 'react-redux';
import SelectList from './SelectList';
import AddNewTask from './AddNewTask';
import Tasks from './Tasks';
import { RootState } from '../store/store';
const MainContent: FC = () => {
  const selectedList = useSelector((state: RootState) => state.list.selectedList);
  return(
    <div className="column is-9">
      <div className="box">
        <SelectList />
        {
          selectedList &&
            <Fragment>
              <AddNewTask list={selectedList} />
              <hr />
              <Tasks tasks={selectedList.tasks} />
            </Fragment>
        }
      </div>
    </div>
  );
}
export default MainContent;

Этот компонент очень прост. Он выведет основной контент рядом с боковой панелью. Нам просто нужно импортировать компоненты задачи и добавить их в этот компонент.

И, наконец, нам нужно перенести этот компонент и модальные окна задачи в компонент App.

import React, { FC } from 'react';
import { useSelector } from 'react-redux';
import './App.css';
import Header from './components/Header';
import Sidebar from './components/Sidebar';
import Notification from './components/Notification';
import { RootState } from './store/store';
import DeleteListModal from './components/DeleteListModal';
import EditListModal from './components/EditListModal';
import MainContent from './components/MainContent';
import EditTaskModal from './components/EditTaskModal';
import DeleteTaskModal from './components/DeleteTaskModal';
const App: FC = () => {
  const notificationMsg = useSelector((state: RootState) => state.notification.message);
  const listIdToDelete = useSelector((state: RootState) => state.list.listIdToDelete);
  const listToEdit = useSelector((state: RootState) => state.list.listToEdit);
  const taskToEdit = useSelector((state: RootState) => state.list.taskToEdit);
  const taskToDelete = useSelector((state: RootState) => state.list.taskToDelete);
  return (
    <div className="App">
      <Header title="Task List App" subtitle="Create some lists and add some tasks to each list" />
      <div className="container px-5">
        <div className="columns">
          <Sidebar />
          <MainContent />
        </div>
      </div>
      <Notification msg={notificationMsg} />
      {listIdToDelete && <DeleteListModal listId={listIdToDelete} />}
      {listToEdit && <EditListModal list={listToEdit} />}
      {taskToEdit && <EditTaskModal taskToEdit={taskToEdit} />}
      {taskToDelete && <DeleteTaskModal taskToDelete={taskToDelete} />}
    </div>
  );
}
export default App;

Теперь вы можете выбрать список и добавить в него несколько задач.

И это все. Несмотря на то, что это приложение маленькое, на самом деле оно содержит много кода.

Предварительный просмотр: https://apps.damirpristav.com/task-list-react-typescript/

Код: https://github.com/damirpristav/task-list-react-typescript