Introduzione al Hook useContext per la condivisione di uno stato
Immaginiamo di avere un’applicazione React composta da diversi componenti, che hanno bisogno di accedere alla stessa fonte di dati. Una soluzione per evitare di dover passare tali informazioni tra i componenti, sarebbe quella di utilizzare il contesto (context) di React.
Nel seguente esempio creiamo una applicazione chiamata “task list”, un elenco di cose da fare durante la giornata che possiamo marcare come “fatto”. Le operazioni permesse saranno quindi:
- Inserimento
- Eliminazione
- Marca come fatto
Per creare un contesto in React creiamo un file apposito chiamato TaskListContext.jsx contenente:
import React, { createContext } from 'react';
const TaskListContext = createContext();
export default TaskListContext;
Le funzione createContext() si occuperà di creare il contenitore di dati (non tipizzato) che popoleremo più avanti.
Possiamo definire un componente “Provider” che avrà il compito di fornire i dati del contesto e le funzioni che devono essere condivisi tra i componenti (per la manipolazione del contesto).
Ad esempio, creiamo il file TaskListProvider.jsx:
import React, { createContext, useContext, useState } from 'react';
import TaskListContext from './TaskListContext';
// Creiamo un componente wrapper che utilizza lo state per la tasklist
const TaskListProvider = ({ children }) => {
const [tasks, setTasks] = useState([]);
const addTask = (newTask) => {
setTasks([...tasks, newTask]);
};
const completeTask = (taskId) => {
setTasks(
tasks.map((task) =>
task.id === taskId ? { ...task, completed: true } : task
)
);
};
const deleteTask = (taskId) => {
setTasks(tasks.filter((task) => task.id !== taskId));
};
return (
<TaskListContext.Provider
value={{ tasks, addTask, completeTask, deleteTask }}
>
{children}
</TaskListContext.Provider>
);
};
export default TaskListProvider;
In questo esempio introduciamo le funzioni addTask, completeTask e deleteTask che si occuperanno di marcare il task come completo e di eliminarlo dalla lista. All’interno del Context veranno inseriti automaticamente i dati che sono inseriti nello stato del provider.
Poniamo l’attenzione sull’innesto dei dati e delle funzioni nel provider:
return (
<TaskListContext.Provider
value={{ tasks, addTask, completeTask, deleteTask }}
>
{children}
</TaskListContext.Provider>
);
Grazie all’utilizzo del provider (che vedremo più avanti), ogni figlio (children) che racchiudiamo all’interno dei tag avranno a disposizione le funzioni: addTask, completeTask, deleteTask e l’elenco dei dati contenuti all’inteno di task.
Creiamo a questo punto due nuovi componenti funzionali che andranno ad operare sullo stato contenuto nel context.
Ecco il componente AddTask.jsx che contiene il form per l’inserimento di un nuovo Task:
import React, { useState, useContext } from 'react';
import TaskListContext from './TaskListContext';
// Creiamo il componente che aggiunge una nuova task
const AddTask = () => {
const [newTask, setNewTask] = useState('');
const { addTask } = useContext(TaskListContext);
const handleSubmit = (e) => {
e.preventDefault();
addTask({ id: Date.now(), description: newTask, completed: false });
setNewTask('');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Aggiungi una nuova task"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button type="submit">Aggiungi</button>
</form>
);
};
export default AddTask;
Come possiamo vedere: const { addTask } = useContext(TaskListContext); mi permette di accedere alla funzione presente all’interno del contesto, si tratta di un filtro per evitare di accedere a tutte le funzionalità presenti nel provider.
Creiamo il componente che visualizzerà l’elenco dei task chiamato TaskList.jsx:
import React, { useState, useContext } from 'react';
import TaskListContext from './TaskListContext';
// Creiamo il componente che mostra le task e le marca come completate o le elimina
const TaskList = () => {
const { tasks, completeTask, deleteTask } = useContext(TaskListContext);
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>
<input
type="checkbox"
checked={task.completed}
onChange={() => completeTask(task.id)}
/>
<span style={{ textDecoration: task.completed ? 'line-through' : 'none' }}>
{task.description}
</span>
<button onClick={() => deleteTask(task.id)}>Elimina</button>
</li>
))}
</ul>
);
};
export default TaskList;
A differenza del componente precedente, questo componente deve accedere ai dati e le funzioni di completeTask e deleteTask:
--> const { tasks, completeTask, deleteTask } = useContext(TaskListContext);
A questo punto possiamo andare in App.js e dichiarare il provider che si occuperà di generare il context ed iniettarlo all’interno dei suoi Componenti Funzionali figli:
import TaskList from './components/Context/TaskList';
import AddTask from './components/Context/AddTask';
import TaskListProvider from './components/Context/TaskListProvider';
function App() {
return (
<>
<TaskListProvider>
<AddTask />
<TaskList />
</TaskListProvider>
</>
);
}
export default App;