Le applicazioni di To-Do List sono uno degli esempi più classici e utili per imparare a sviluppare interfacce utente dinamiche e interattive. Attraverso la gestione dello stato, l'uso di librerie di stato globale e la persistenza dei dati, si possono creare applicazioni robuste e scalabili. In questa guida, svilupperemo una To-Do List con React, Redux Toolkit e Local Storage, sfruttando ciascun strumento per ottimizzare la gestione del flusso di dati e garantire che i task siano memorizzati anche dopo il riavvio dell'applicazione.
Configurazione del progetto React
Iniziamo creando un nuovo progetto React che ci fornirà una base solida per il nostro lavoro. Per farlo, possiamo utilizzare il comando npx create-react-app che imposta automaticamente l'ambiente di sviluppo:
npx create-react-app todo-list
cd todo-list
Una volta creato il progetto, dovremo installare Redux Toolkit e React Redux, che ci aiuteranno a gestire lo stato globale dell'applicazione. Redux è un popolare strumento per il controllo dello stato, mentre React Redux è la libreria che permette di integrarlo perfettamente con React.
npm install @reduxjs/toolkit react-redux
Configurazione di Redux
Per iniziare con Redux, dobbiamo configurare lo "slice" che gestirà lo stato della nostra To-Do List. Creiamo una cartella features e aggiungiamo un file JavaScript chiamato todosSlice.js. Questo slice definirà le azioni per aggiungere, eliminare e segnare come completato i task, oltre a specificare lo stato iniziale.
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
tasks: JSON.parse(localStorage.getItem('tasks')) || [],
};
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTask: (state, action) => {
state.tasks.push(action.payload);
localStorage.setItem('tasks', JSON.stringify(state.tasks));
},
deleteTask: (state, action) => {
state.tasks = state.tasks.filter(task => task.id !== action.payload);
localStorage.setItem('tasks', JSON.stringify(state.tasks));
},
toggleTask: (state, action) => {
const task = state.tasks.find(task => task.id === action.payload);
if (task) task.completed = !task.completed;
localStorage.setItem('tasks', JSON.stringify(state.tasks));
},
},
});
export const { addTask, deleteTask, toggleTask } = todosSlice.actions;
export default todosSlice.reducer;
Nel codice sopra, abbiamo uno stato iniziale con cui otteniamo i task salvati nel Local Storage (se presenti) altrimenti inizializziamo come array vuoto. I Reducers definiscono invece le azioni che manipolano lo stato per aggiunge un nuovo task, eliminare un task specificato e segnare un task come completato o incompleto. Ogni volta che lo stato dei task cambia, aggiorniamo il Local Storage per mantenere i dati tra i riavvii dell'applicazione (Persistenza).
Configurazione dello Store
Ora, dobbiamo configurare lo store di Redux, che manterrà il nostro stato globale. In React, questo viene fatto nel file src/app/store.js, dove importiamo il reducer creato nel file todosSlice.js e lo passiamo al nostro store.
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todosSlice';
export const store = configureStore({
reducer: {
todos: todosReducer,
},
});
Connessione di Redux a React
Il prossimo passo è integrare Redux con l'applicazione React. Per farlo, dobbiamo usare il Provider di React Redux nel nostro file index.js che renderizzerà l'app all'interno del contesto dello store Redux.
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './app/store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Creazione dei componenti React
Una volta configurato Redux, possiamo concentrarci sulla creazione dei componenti. La nostra To-Do List avrà due componenti principali: AddTask.js, un componente per aggiungere nuovi task e TaskList.js, un componente che visualizza i task presenti nella lista.
Componente AddTask.js
addTask
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { addTask } from '../features/todosSlice';
const AddTask = () => {
const [task, setTask] = useState('');
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (task.trim()) {
dispatch(addTask({ id: Date.now(), text: task, completed: false }));
setTask('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={task}
onChange={(e) => setTask(e.target.value)}
placeholder="Aggiungi un nuovo task"
/>
<button type="submit">Aggiungi</button>
</form>
);
};
export default AddTask;
Componente TaskList.js
Il componente TaskList recupera i task dallo stato globale tramite useSelector e li visualizza. Ogni task può essere selezionato per essere segnato come completato o eliminato.
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { deleteTask, toggleTask } from '../features/todosSlice';
const TaskList = () => {
const tasks = useSelector((state) => state.todos.tasks);
const dispatch = useDispatch();
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>
<span
onClick={() => dispatch(toggleTask(task.id))}
style={{
textDecoration: task.completed ? 'line-through' : 'none',
cursor: 'pointer',
}}
>
{task.text}
</span>
<button onClick={() => dispatch(deleteTask(task.id))}>
Elimina
</button>
</li>
))}
</ul>
);
};
export default TaskList;
Componente principale
Infine, aggiorniamo il file App.js per includere i componenti AddTask e TaskList che sono il cuore della nostra applicazione.
import React from 'react';
import './App.css';
import AddTask from './components/AddTask';
import TaskList from './components/TaskList';
function App() {
return (
<div className="App">
<h1>To-Do List</h1>
<AddTask />
<TaskList />
</div>
);
}
export default App;
Styling con CSS
Per migliorare l'aspetto dell'interfaccia utente, possiamo aggiungere alcune regole CSS nel file App.css.
.App {
text-align: center;
font-family: Arial, sans-serif;
}
form {
margin: 20px;
}
input {
padding: 10px;
font-size: 16px;
width: 70%;
}
button {
padding: 10px 20px;
font-size: 16px;
margin-left: 10px;
cursor: pointer;
}
ul {
list-style: none;
padding: 0;
}
li {
margin: 10px 0;
display: flex;
justify-content: space-between;
align-items: center;
}
Debugging e test del progetto React
Per il debugging, Redux offre una potente funzionalità chiamata Redux DevTools che consente di monitorare lo stato e le azioni. È possibile utilizzarla per verificare che tutte le modifiche al nostro stato siano corrette. Inoltre, possiamo scrivere test per assicurarci che i componenti e le azioni funzionino correttamente, utilizzando strumenti come Jest o Testing Library.
Conclusione
Abbiamo creato una To-Do List completa utilizzando React, Redux Toolkit e Local Storage. L'app gestisce lo stato dei task, permette di aggiungere, eliminare e segnare come completati i task, e salva i dati nel localStorage per garantire la persistenza tra i riavvii dell'applicazione. Questo esempio rappresenta un'ottima base per creare applicazioni più complesse e può essere facilmente esteso aggiungendo funzionalità come filtri, categorie di task o persistenza tramite un backend server.