A simple React Todo List with Zustand, Material/UI and persisting data with Zustand Midleware

Leandro A. Siqueira
3 min readSep 25, 2024

--

The final project is here: https://github.com/leandroaps/react-todo-app-zustand

Creating the project base

To start, create the project react-todo-app-zustand using the comand below on your terminal:

yarn create vite@latest react-todo-app-zustand --template react
  • Choose React
  • Select TypeScript

Go to the folder of your project and run yarn to install all the dependencies

cd react-todo-app-zustand && yarn

After that, I creates some folder to keep the things organized:

  • components
  • store
  • theme (only if you want to use Material/UI)
  • types

Material/UI

Install the Material/UI using the command:

yarn add @mui/material @emotion/react @emotion/styled

Create a index.tsx file inside the folder theme, with the content, this will handle a small theme for Material/UI.

import { red } from '@mui/material/colors';
import { createTheme } from '@mui/material/styles';

const theme = createTheme({
palette: {
primary: {
main: '#556cd6'
},
secondary: {
main: '#19857b'
},
error: {
main: red.A400
}
}
});

export default theme;

Adjust our main.tsx file, adding the theme and the CssBaseline, use the content below:

import { CssBaseline } from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import App from './App';
import theme from './theme/';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
</React.StrictMode>
);

Zustand

Install zustand using

yarn add zustand

Inside the store folder, create a index.tsx file with the code, this will handle our store data, with the persist method from zustand/midleware

import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { IToDo } from '../types';

const store = create()(
devtools(
persist(
(set) => ({
todos: [],
addTodo: (todo: IToDo) => {
set((state: { todos: IToDo[] }) => ({
todos: [...state.todos, { id: Date.now(), text: todo, completed: false }]
}));
},
removeTodo: (id: number) => {
set((state: { todos: IToDo[] }) => ({
todos: state.todos.filter((todo: IToDo) => todo.id !== id)
}));
},
toggleTodo: (id: number) => {
set((state: { todos: IToDo[] }) => ({
todos: state.todos.map((todo: IToDo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
}));
}
}),
{ name: 'bearStore' }
)
)
);
export default store;

TypeScript

Adds some types to our project, inside the folder types, create a index.tsx file with the content:

export interface IToDo {
id: number;
completed: boolean;
text: string;
}

To Do List

For the main component, create a folder named TodoList inside the folder components and inside that folder, create our TodoList.tsx file with the content:

import { Button, Input, List, ListItem, Typography } from '@mui/material';
import { memo, useState } from 'react';
import store from '../../store';
import { IToDo } from '../../types/';

const TodoList = () => {
const { todos, addTodo, removeTodo, toggleTodo } = store();

const [newTodo, setNewTodo] = useState('');

const handleAddTodo = () => {
if (newTodo.trim()) {
addTodo(newTodo);
setNewTodo(''); // Clear the input after adding
}
};

return (
<div>
<Typography variant="h1" component="h1" sx={{ mb: 2 }}>
Todo List
</Typography>
<Input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Add a new todo"
/>
<Button onClick={handleAddTodo}>Add</Button>

<List>
{todos.map((todo: IToDo) => (
<ListItem key={todo.id}>
<Typography
style={{
textDecoration: todo.completed ? 'line-through' : 'none',
cursor: 'pointer'
}}
onClick={() => toggleTodo(todo.id)}
>
{todo.text}
</Typography>
<Button onClick={() => removeTodo(todo.id)}>Delete</Button>
</ListItem>
))}
</List>
</div>
);
};

export default memo(TodoList);

App

Connect the TodoList component to our using the content:

import { Container } from '@mui/material';
import { memo } from 'react';
import TodoList from './components/TodoList/TodoList';

function App() {
return (
<Container>
<TodoList />
</Container>
);
}

export default memo(App);

I added some memo to our code to keep the store memoized, this will improve performance and the loading of the components avoiding re-renders.

--

--

Leandro A. Siqueira
Leandro A. Siqueira

Written by Leandro A. Siqueira

Well, I woke up this morning and I got myself a beer!

No responses yet