Todo list with React, Redux, TypeScript and drag and drop
Tags: javascript, typescript, react, redux
Published: 2022-02-05 15:07:34

In this article I will present how to make simple ToDo application with React, Redux, TypeScript and drag and drop library.

Basicily, Redux is overkill to that simple application as ToDo application but a lot of technical tasks before "job Interview" or even sometimes during live coding require that approach to check your knowledge about Redux + React. 

I will use React + TypeScript, I prefer that style of coding on the frontend but if you want you can use JavaScript without TS :)

To styling components I will use [MUI ](https://mui.com) with version 5.4.0 and to the drag and drop events I will use [react-beautiful-dnd](https://github.com/atlassian/react-beautiful-dnd) and [redux-toolkit](https://github.com/reduxjs/redux-toolkit) for redux.

&nbsp;

#### Requirements 
- basic knowledge about JavaScript/TypeScript 
- basic knowledge React 
- basic knowledge Redux
- basic knowledge about concept css-in-js

These points are not required but if you know how these things works it will be better for your because I will not explain how React / Typescript / MUI works.

&nbsp;

### So, the first step is creating simple [Create-React-App](https://create-react-app.dev/docs/getting-started/#creating-a-typescript-app) with TS template and redux.

```bash
npx create-react-app react-todo-list-redux --template redux-typescript
```
After installation install another required dependencies.

```bash
npm i @emotion/react @emotion/styled @mui/icons-material @mui/material react-beautiful-dnd uuid
```
and to devDependencies

```bash
npm i @types/react-beautiful-dnd @types/uuid -D
```
After installation all required dependencies your application folder should look similar to this: 

![started-package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vgmst4fubqj6vq6xbpwh.png)

and you can delete not nessesary files/folders for this project as 

- App.css
- App.test.tsx
- index.css
- logo.svg
- serviceWorker.ts
- setupTests.ts
- features folder
- app folder

We will not use them :)

After that create folder `redux` and inside this folder create another folder called `store` and inside store folder create file `index.ts`.

```bash
cd src && mkdir redux && cd redux && mkdir store && cd store && touch index.ts
```
and copy this code below to store/index.ts

```typescript
import { configureStore, combineReducers } from '@reduxjs/toolkit';

export const store = configureStore({
  reducer: combineReducers({}),
});

export type StoreDispatch = typeof store.dispatch;
export type StoreState = ReturnType<typeof store.getState>;
```
The code above is storing your reducers and combine them to use in our application but for now reducers are empty.

### The next step is start project

If you deleted files you probably will get some errors about deleted files. Just remove every not necessary imports in the App.tsx and in the index.tx files.

To index.tsx import `CssBaseLine` component from `@mui/material/CssBaseline` and add as a child to the `<Provider />`. This component provides normalized styling to the components.

```typescript
import CssBaseline from '@mui/material/CssBaseline';
```

![app-and-index-photo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/89p7vg8wpdb1zfo4dzmq.png)
  
After that type in your console: 

```bash
npm start
```
If everything is going well, you will see blank page and after that open your console and you probably see error called `Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.` - this error is complety fine at the moment because we don't have any reducers yet! 

### Create new folders

We are gonna create new folders called `components`, `types` and `slice` folder in the src directory. Inside types folder create `index.ts` file.

In the root directory type:

```bash
cd src && mkdir components && mkdir types && cd types && touch index.ts && cd ../redux && mkdir slice
```

### Basic Styling in App.tsx

Open file App.tsx and add this code bellow to your file. This is a basic styling for with container and grid.

```typescript
// src/App.tsx
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';

function App() {
  return (
    <Container>
      <Typography textAlign='center' variant='h3' mt={3} mb={5}>
        This is a ToDo APP with Redux
      </Typography>
      <Grid container spacing={3} justifyContent='center'>
        <Grid item md={4}>
          ...
        </Grid>
        <Grid item md={4}>
          ...
        </Grid>
        <Grid item md={4}>
          ...
        </Grid>
      </Grid>
    </Container>
  );
}

export default App;
```
### Create new files in components folder

Go to the components folder and insiade create new folder called columns and inside this folder create 3 files called: `ToDo.tsx`, `InProgress.tsx` and `Done.tsx`.

After after that go to folder components and create file called `ColumnLayout.tsx` and copy code below inside.

```typescript
// src/components/ColumnLayout.tsx
import React, { useState } from 'react';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import Checkbox from '@mui/material/Checkbox';
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import Alert from '@mui/material/Alert';
import Collapse from '@mui/material/Collapse';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import { useDispatch } from 'react-redux';
import { StoreDispatch } from '../redux/store';
import { IColumnLayoutProps } from '../types';

const ColumnLayout: React.FC<IColumnLayoutProps> = ({
  labelText,
  addHandler,
  removeHandler,
  completedHandler,
  selectorState,
  droppableId,
}) => {
  const [isError, setIsError] = useState({
    isShow: false,
    text: '',
  });
  const [textDescription, setTextDescription] = useState('');
  const dispatch = useDispatch<StoreDispatch>();

  const handleOnChange = ({
    target: { value },
  }: React.ChangeEvent<HTMLInputElement>) => {
    setTextDescription(value);
    setIsError({
      isShow: value.length > 200,
      text:
        value.length > 200
          ? 'The input value cannot be more than 200 characters'
          : '',
    });
  };

  const handleOnBlur = () => {
    setIsError({ ...isError, isShow: false });
  };

  const handleOnClick = () => {
    if (!isError) {
      dispatch(addHandler(textDescription));
      setTextDescription('');
    }
  };

  const handleInputKeyDown = ({
    target,
    key,
  }: React.KeyboardEvent<HTMLInputElement>) => {
    if (key === 'Enter') {
      if (
        (target as HTMLInputElement).value.length > 0 &&
        (target as HTMLInputElement).value.length <= 200
      ) {
        handleOnClick();
      } else {
        setIsError({
          isShow: true,
          text: 'The input value cannot be empty',
        });
      }
    }
  };

  return (
    <Box borderRadius={1} width='100%' sx={{ boxShadow: 2, p: 3 }}>
      <TextField
        fullWidth
        label={labelText}
        onChange={handleOnChange}
        onBlur={handleOnBlur}
        onKeyDown={handleInputKeyDown}
        value={textDescription}
        variant='outlined'
        size='small'
      />

      <Collapse in={isError.isShow}>
        <Alert severity='error' sx={{ my: 1 }}>
          {isError.text}
        </Alert>
      </Collapse>

      <Box width='100%' display='flex' justifyContent='center'>
        <Button
          size='medium'
          sx={{ my: 1, maxWidth: 200 }}
          variant='outlined'
          color='primary'
          fullWidth
          onClick={handleOnClick}
          disabled={
            textDescription.length === 0 || textDescription.length > 200
          }
        >
          Add Item
        </Button>
      </Box>

      <List sx={{ minHeight: '300px' }}>
        {selectorState.map(
          ({ id, text, isFinished, createdAt, updatedAt }, index: number) => {
            return (
              <ListItem
                sx={{
                  position: 'relative',
                  border: '1px solid #989898',
                  bgcolor: '#fff',
                  my: 1,
                  borderRadius: '3px',
                  '& .MuiTypography-root': {
                    display: 'flex',
                    alignItems: 'center',
                  },
                }}
              >
                <ListItemText
                  sx={{
                    textDecoration: isFinished ? 'line-through' : 'none',
                    wordBreak: 'break-word',
                  }}
                >
                  <IconButton sx={{ p: 1, mr: 1 }}>
                    <ArrowDownwardIcon />
                  </IconButton>

                  <Box
                    component='span'
                    width='100%'
                    position='absolute'
                    top='0'
                    fontSize='.7rem'
                  >
                    {updatedAt ? 'Updated' : 'Created'} at:{' '}
                    {updatedAt || createdAt}
                  </Box>

                  <Box component='span' width='100%'>
                    {text}
                  </Box>

                  <Box display='flex' component='span'>
                    <IconButton onClick={() => dispatch(removeHandler(id))}>
                      <DeleteIcon />
                    </IconButton>
                    <Checkbox
                      edge='end'
                      value={isFinished}
                      checked={isFinished}
                      inputProps={{ 'aria-label': 'controlled' }}
                      onChange={() =>
                        dispatch(
                          completedHandler({
                            isFinished: !isFinished,
                            id,
                            updatedAt: new Date().toLocaleString(),
                          })
                        )
                      }
                    />
                  </Box>
                </ListItemText>
              </ListItem>
            );
          }
        )}
      </List>
    </Box>
  );
};

export default ColumnLayout;
```
##### As an explanation
Code above contains styling, events for each column ToDo, In Progress and Done. It's also contains props which will be used in the redux reducers to store states and update everything dynamically.

After that go to the `types` folder and open `index.ts` and copy this code below which contains model for state reducers, types for ColumnLayout component and types ActionSlice/TUpdateTextShowed for PayloadAction from @reduxjs/toolkit:

```typescript
// src/types/index.ts
import { AnyAction } from '@reduxjs/toolkit';

export interface IModel {
  id: string;
  text: string;
  isFinished: boolean;
  createdAt?: string;
  updatedAt?: string;
  isTextShowed?: boolean;
}

export type TActionSlice = Omit<IModel, 'text'>;
export type TUpdateTextShowed = Omit<TActionSlice, 'isFinished'>;

export interface IColumnLayoutProps {
  labelText?: string;
  addHandler: (v: string) => AnyAction;
  removeHandler: (v: string) => AnyAction;
  completedHandler: (v: TActionSlice) => AnyAction;
  selectorState: IModel[];
  droppableId: string;
  updateTextShowed: (v: TUpdateTextShowed) => AnyAction;
}
```

### Update columns files

Go to files `Done.tsx`, `ToDo.tsx` and `InProgress.tsx` in columns folder and copy code below and paste into them:

```react
// src/components/columns/InProgress.tsx
import Typography from '@mui/material/Typography';
import { useSelector } from 'react-redux';
import { StoreState } from '../../redux/store';
import { inProgressSlice } from '../../redux/slice/inProgress';
import ColumnLayout from '../ColumnLayout';

export function InProgressColumn() {
  const { inProgress } = useSelector((state: StoreState) => state);

  const {
    actions: { completeStatus, remove, add, updateTextShowed },
  } = inProgressSlice;

  return (
    <>
      <Typography mb={3}>All inProgress tasks: {inProgress.length}</Typography>
      <ColumnLayout
        droppableId='inProgress'
        labelText="Type 'in progress' item"
        completedHandler={completeStatus}
        removeHandler={remove}
        addHandler={add}
        selectorState={inProgress}
        updateTextShowed={updateTextShowed}
      />
    </>
  );
}
```
```react
// src/components/columns/Done.tsx
import Typography from '@mui/material/Typography';
import { useSelector } from 'react-redux';
import { StoreState } from '../../redux/store';
import { doneSlice } from '../../redux/slice/done';
import ColumnLayout from '../ColumnLayout';

export function DoneColumn() {
  const { done } = useSelector((state: StoreState) => state);
  const {
    actions: { completeStatus, remove, add, updateTextShowed },
  } = doneSlice;

  return (
    <>
      <Typography mb={3}>All done tasks: {done.length}</Typography>
      <ColumnLayout
        droppableId='done'
        labelText="Type 'done' item"
        completedHandler={completeStatus}
        removeHandler={remove}
        addHandler={add}
        selectorState={done}
        updateTextShowed={updateTextShowed}
      />
    </>
  );
}
```
```react
// src/components/columns/ToDo.tsx
import Typography from '@mui/material/Typography';
import { useSelector } from 'react-redux';
import { StoreState } from '../../redux/store';
import { todoSlice } from '../../redux/slice/todo';
import ColumnLayout from '../ColumnLayout';

export function ToDoColumn() {
  const { todo } = useSelector((state: StoreState) => state);
  const {
    actions: { completeStatus, remove, add, updateTextShowed },
  } = todoSlice;

  return (
    <>
      <Typography mb={3}>All todo tasks: {todo.length}</Typography>
      <ColumnLayout
        droppableId='todo'
        labelText="Type 'to do' item"
        completedHandler={completeStatus}
        removeHandler={remove}
        addHandler={add}
        selectorState={todo}
        updateTextShowed={updateTextShowed}
      />
    </>
  );
}
```
##### As an explanation
Code above contains resuable ColumnLayout component which have props for update redux state.

After that go to App.tsx file and change every `...` to the column components.

```react
// src/App.tsx
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import { ToDoColumn } from './components/columns/ToDo';
import { InProgressColumn } from './components/columns/InProgress';
import { DoneColumn } from './components/columns/Done';

function App() {
  return (
    <Container>
      <Typography textAlign='center' variant='h3' mt={3} mb={5}>
        This is a ToDo APP with Redux
      </Typography>
      <Grid container spacing={3} justifyContent='center'>
        <Grid item md={4}>
          <ToDoColumn />
        </Grid>
        <Grid item md={4}>
          <InProgressColumn />
        </Grid>
        <Grid item md={4}>
          <DoneColumn />
        </Grid>
      </Grid>
    </Container>
  );
}

export default App;
```

### Create slices in the redux folder

If you run your application you will see a few errors about not exists code. We will gonna fix this now.

Go to the redux and slice folder and create 3 files called: `done.ts`, `inProgress.ts` and `todo.ts`.

Each files contains `own` state from createSlice() method and actions with reducers. So we need to create methods that allows to add/update/remove state in the columns.

```react
// src/redux/slice/done.ts
import { createCustomSlice } from './customSlice';

export const doneSlice = createCustomSlice('done');
```

```react
// src/redux/slice/inProgress.ts
import { createCustomSlice } from './customSlice';

export const inProgressSlice = createCustomSlice('progress');
```

```react
// src/redux/slice/todo.ts
import { createCustomSlice } from './customSlice';

export const todoSlice = createCustomSlice('todo');
```

and create file `customSlice.ts` in the slice folder

```typescript
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { v4 as uuidv4 } from 'uuid';
import { TActionSlice, TUpdateTextShowed, IModel } from '../../types';

const initialState: IModel[] = [];

export const createCustomSlice = (name: string) => {
  const {
    actions: { add, remove, completeStatus, reorder, update, updateTextShowed },
    reducer,
  } = createSlice({
    name,
    initialState,
    reducers: {
      add: {
        reducer: (state, action: PayloadAction<IModel>) => {
          state.push(action.payload);
        },
        prepare: (text: string) => ({
          payload: {
            id: uuidv4(),
            text,
            isFinished: false,
            createdAt: new Date().toLocaleString(),
            isTextShowed: false,
          } as IModel,
        }),
      },
      update(state, action) {
        state.splice(
          action.payload.destination.index,
          0,
          action.payload.filterState
        );
      },
      remove(state, action: PayloadAction<string>) {
        const index = state.findIndex(({ id }) => id === action.payload);
        state.splice(index, 1);
      },
      completeStatus(state, action: PayloadAction<TActionSlice>) {
        const index = state.findIndex(({ id }) => id === action.payload.id);
        state[index].isFinished = action.payload.isFinished;
        state[index].updatedAt = action.payload.updatedAt;
      },
      updateTextShowed(state, action: PayloadAction<TUpdateTextShowed>) {
        const index = state.findIndex(({ id }) => id === action.payload.id);
        state[index].isTextShowed = action.payload.isTextShowed;
      },
      reorder(state, action) {
        const [removed] = state.splice(action.payload.source.index, 1);
        state.splice(action.payload.destination.index, 0, removed);
      },
    },
  });

  return {
    actions: { add, remove, completeStatus, reorder, update, updateTextShowed },
    reducer,
  };
};
```
This file contains all nessesary reducers, actions and initial state required to propery works redux state mangement to update store. Each file corresponds to `own` state.

After update files in the slice folder, you need to go to to the store folder and in the index.ts file import reducers from slice folder and combine them together in the store.

```react
// src/redix/store/index.ts
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import { doneSlice } from "../slice/done";
import { inProgressSlice } from "../slice/inProgress";
import { todoSlice } from "../slice/todo";

export const store = configureStore({
  reducer: combineReducers({
    done: doneSlice.reducer,
    inProgress: inProgressSlice.reducer,
    todo: todoSlice.reducer,
  }),
});

export type StoreDispatch = typeof store.dispatch;
export type StoreState = ReturnType<typeof store.getState>;
```
If everything went well you should have a working ToDo app but still without drag and drop!

![basic-app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/09fl835uwnsufu4u7t3e.png)
 
### Update files about drag and drop!

We need to modify a few files to get working drag and drop in our simple application. We are using `react-beautiful-dnd` which required a `DragDropContext` as parent component to each `Draggable` component.

So first, you need to go to `App.tsx` and replace existing code with this:

```react
// src/App.tsx
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import { DragDropContext, DropResult } from 'react-beautiful-dnd';
import { useDispatch, useSelector } from 'react-redux';
import { ToDoColumn } from './components/columns/ToDo';
import { DoneColumn } from './components/columns/Done';
import { InProgressColumn } from './components/columns/InProgress';
import { todoSlice as todo } from './redux/slice/todo';
import { inProgressSlice as inProgress } from './redux/slice/inProgress';
import { doneSlice as done } from './redux/slice/done';
import { StoreState } from './redux/store';
import { IModel } from './types';

type TAllSilces = 'todo' | 'inProgress' | 'done';

function App() {
  const dispatch = useDispatch();
  const appState = useSelector((state: StoreState) => state);

  const onDragEnd = (result: DropResult) => {
    if (!result.destination) {
      return;
    }

    const { destination, source, draggableId } = result;
    const allSlices = { todo, inProgress, done };

    if (destination.droppableId === source.droppableId) {
      dispatch(
        allSlices[destination.droppableId as TAllSilces].actions.reorder(result)
      );
    } else {
      const [filterState] = (
        (appState as any)[source.droppableId] as IModel[]
      ).filter(({ id }) => id === draggableId);

      dispatch(
        allSlices[source.droppableId as TAllSilces].actions.remove(draggableId)
      );
      dispatch(
        allSlices[destination.droppableId as TAllSilces].actions.update({
          ...result,
          filterState,
        })
      );
    }
  };

  return (
    <Container>
      <Typography textAlign='center' variant='h3' mt={3} mb={5}>
        This is a ToDo APP with Redux
      </Typography>{' '}
      <Grid container spacing={3} justifyContent='center'>
        <DragDropContext onDragEnd={(res) => onDragEnd(res)}>
          <Grid item md={4}>
            <ToDoColumn />
          </Grid>
          <Grid item md={4}>
            <InProgressColumn />
          </Grid>
          <Grid item md={4}>
            <DoneColumn />
          </Grid>
        </DragDropContext>
      </Grid>
    </Container>
  );
}

export default App;
```
The above file was updated about `DragDropContext` as a mentioned eariler and also with onDragEnd function which is a function when dragging and dropping are over.

This function also check current id's  of droppable with destination and source. If destination is equal to source and indexes are different so it gona change position of dragging element in the list. If destinatnion and source is not equal so there are dynamic funtions where depends where droppableId is equal that will fire update and remove reducers to update store.

Next you have to go to the `ColumnLayout.tsx` file and replace existing code with this:

```react
// src/components/ColumnLayout.tsx
import React, { useState } from 'react';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import ListItemText from '@mui/material/ListItemText';
import Checkbox from '@mui/material/Checkbox';
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import Alert from '@mui/material/Alert';
import Collapse from '@mui/material/Collapse';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import { useDispatch } from 'react-redux';
import { Droppable, Draggable } from 'react-beautiful-dnd';
import { StoreDispatch } from '../redux/store';
import { IColumnLayoutProps } from '../types';

const ColumnLayout: React.FC<IColumnLayoutProps> = ({
  labelText,
  addHandler,
  removeHandler,
  completedHandler,
  selectorState,
  droppableId,
  updateTextShowed,
}) => {
  const [isError, setIsError] = useState({
    isShow: false,
    text: '',
  });

  const [textDescription, setTextDescription] = useState('');
  const dispatch = useDispatch<StoreDispatch>();

  const handleOnChange = ({
    target: { value },
  }: React.ChangeEvent<HTMLInputElement>) => {
    setTextDescription(value);

    setIsError({
      isShow: value.length > 200,
      text:
        value.length > 200
          ? 'The input value cannot be more than 200 characters'
          : '',
    });
  };

  const handleOnBlur = () => {
    setIsError({ ...isError, isShow: false });
  };

  const handleOnClick = () => {
    if (!isError.isShow) {
      dispatch(addHandler(textDescription));
      setTextDescription('');
    }
  };

  const handleInputKeyDown = ({
    target,
    key,
  }: React.KeyboardEvent<HTMLInputElement>) => {
    if (key === 'Enter') {
      if (
        (target as HTMLInputElement).value.length > 0 &&
        (target as HTMLInputElement).value.length <= 200
      ) {
        handleOnClick();
      } else {
        setIsError({
          isShow: true,
          text: 'The input value cannot be empty',
        });
      }
    }
  };

  return (
    <Box borderRadius={1} width='100%' sx={{ boxShadow: 2, p: 3 }}>
      <TextField
        fullWidth
        label={labelText}
        onChange={handleOnChange}
        onBlur={handleOnBlur}
        onKeyDown={handleInputKeyDown}
        value={textDescription}
        variant='outlined'
        size='small'
      />

      <Collapse in={isError.isShow}>
        <Alert severity='error' sx={{ my: 1 }}>
          {isError.text}
        </Alert>
      </Collapse>

      <Box width='100%' display='flex' justifyContent='center'>
        <Button
          size='medium'
          sx={{ my: 1, maxWidth: 200 }}
          variant='outlined'
          color='primary'
          fullWidth
          onClick={handleOnClick}
          onKeyDown={({ key }) => key === 'Enter' && handleOnClick()}
          disabled={
            textDescription.length === 0 || textDescription.length > 200
          }
        >
          Add Item
        </Button>
      </Box>
      <Droppable droppableId={droppableId}>
        {(provided) => (
          <List
            sx={{
              minHeight: '300px',
              li: {
                flexDirection: 'column',
              },
              '& .MuiListItemText-root': {
                width: '100%',
              },
            }}
            ref={provided.innerRef}
            {...provided.droppableProps}
          >
            {selectorState.map(
              (
                { id, text, isFinished, createdAt, updatedAt, isTextShowed },
                index: number
              ) => (
                <Draggable key={id} draggableId={id} index={index}>
                  {(provided, snapshot) => (
                    <ListItem
                      sx={{
                        transition: '.3s ease background-color',
                        color: snapshot.isDragging ? '#fff' : '#000',
                        bgcolor: snapshot.isDragging ? '#000' : '#fff',
                        position: 'relative',
                        border: '1px solid #989898',
                        my: 1,
                        borderRadius: '3px',
                        '& .MuiTypography-root': {
                          display: 'flex',
                          alignItems: 'center',
                        },
                      }}
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                    >
                      <ListItemText
                        sx={{
                          textDecoration: isFinished ? 'line-through' : 'none',
                          wordBreak: 'break-word',
                        }}
                      >
                        <IconButton
                          sx={{ p: 1, mr: 1 }}
                          onClick={() =>
                            dispatch(
                              updateTextShowed({
                                id,
                                isTextShowed: !isTextShowed,
                              })
                            )
                          }
                        >
                          <ArrowDownwardIcon
                            sx={{
                              color: snapshot.isDragging ? '#fff' : '#000',
                              transform: !isTextShowed ? 'rotate(180deg)' : '',
                            }}
                          />
                        </IconButton>

                        <Box
                          component='span'
                          width='100%'
                          position='absolute'
                          top='0'
                          fontSize='.7rem'
                        >
                          {updatedAt ? 'Updated' : 'Created'} at:{' '}
                          {updatedAt || createdAt}
                        </Box>

                        <Box component='span' width='100%'>
                          {text}
                        </Box>

                        <Box display='flex' component='span'>
                          <IconButton
                            onClick={() => dispatch(removeHandler(id))}
                          >
                            <DeleteIcon
                              sx={{
                                color: snapshot.isDragging ? '#fff' : '#000',
                              }}
                            />
                          </IconButton>
                          <Checkbox
                            edge='end'
                            value={isFinished}
                            checked={isFinished}
                            inputProps={{ 'aria-label': 'controlled' }}
                            onChange={() =>
                              dispatch(
                                completedHandler({
                                  isFinished: !isFinished,
                                  id,
                                  updatedAt: new Date().toLocaleString(),
                                })
                              )
                            }
                          />
                        </Box>
                      </ListItemText>
                      <Collapse in={isTextShowed}>
                        You can add here some content{' '}
                        <span role='img' aria-label='emoji'>
                          😍
                        </span>
                      </Collapse>
                    </ListItem>
                  )}
                </Draggable>
              )
            )}
            {provided.placeholder}
          </List>
        )}
      </Droppable>
    </Box>
  );
};

export default ColumnLayout;
```
The file above was updated with `Draggable` and `Droppable` components. Draggable component required unique draggableId to recognize which element is current dragging and index to update state when drag will end. Whereas, the Droppable component required droppableId which also is unique and recognize a `place` when the components are dragged and dropped.

The app structure should looks similar to this in the end:

![app-strucutre](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5463ckb3enc7zlu23ksl.png)
 

-----

### Conclusion

This application is very simple and contains basic approach how to use Redux + React + Drag And Drop. You can freely modify this code and if you find some bugs let me know. 

Here is a link to the github repository with full code:
[https://github.com/pglejzer/react-todo-list-redux](https://github.com/pglejzer/react-todo-list-redux)
and link to the [codesanbox](https://codesandbox.io/s/interesting-wilbur-hx2oi)