Gestire un carrello tramite le context API di React

Le context API di React consentono di gestire semplici stati condivisi tra componenti

Nella versione 16.3.0 di React, già di qualche anno fa, sono state introdotte le context API che consentono di condividere dei valori tra componenti senza doverli esplicitamente passare come parametro.
Una implementazione pratica potrebbe essere quella di condividere il carrello di un sito e-commerce, quello che voglio ottenere nello specifico è che quando l'utente clicca sul pulsante 'Aggiungi al carrello' venga aggiornata la quantità prodotti presente nel'header del sito.

Iniziamo con i due componenti da collegare:
Il primo è il pulsante, presente in ogni pagina prodotto, che accetta come parametro i dati del prodotto da aggiungere al carrello,

AddToCartButton.js

export default function AddToCartButton({product}) {
    return (
        <button>Add to cart</button>
    )
}

Il secondo è l'icona del carrello inclusa nell'header del sito

CartIcon.js

export default function CartIcon() {
    return (
        <Link to="/cart">
            <Icon icon="cart"/>
        </Link>
    )
}

Step 1: Il contesto

La prima cosa da fare è creare il contesto nel quale verranno salvati i dati del carrello. Tramite createContext() andiamo a definire la struttura del contesto, definendo solo la tipologia dei dati. Nel nostro caso il contesto è un oggetto composto da un array che contiene i prodotti presenti nel carrello, e un metodo di cui più avanti definiremo il codice che si occuperà effettivamente di aggiungere i prodotto all'array del carrello.

CartContext.js

import { createContext } from 'react';

export const CartContext = createContext({
    cart: [],
    addToCart: product => { }
});

Step 2: Il provider

Il provider ha lo scopo di rendere disponibili i dati del contesto a tutti i componenti al suo interno e viene già definito dal metodo che abbiamo visto al punto precedente, ma questo passaggio di informazioni avviene solamente al mount dei componenti. I dati che vogliamo gestire sono dinamici pertanto dobbiamo associare al nostro provider uno stato. Andiamo a creare un nuovo componente che si occupi di gestire sia il provider che lo stato

CartContext.js

import { useState, createContext } from 'react';

export const CartContext ...

export default function CartProvider(props) {
    // Set up state
    const [cart, setCart] = useState([]);

    // Update state method
    const addToCart = product => {
        const updatedCart = [...cart, product];
        setCart(updatedCart);
    }

    // Set up Provider with state data
    return (
        <CartContext.Provider value={{
            cart: cart,
            addToCart: addToCart
        }}>
            {props.children}
        </CartContext.Provider>
    )
}

Il metodo AddToCart() è ovviamente una semplificazione, in un sito reale ci sarebbero diversi controlli da fare.

A questo punto non ci resta che includere il provider nella nostra app in modo da racchiudere tutti i componenti che necessitano di accedere al contesto

App.js

...
import CartProvider from 'CartContext';

function App() {
    return (
        ...
        <CartProvider>
            <Header/>
            ...
            <ProductDetail/>
            ...
        </CartProvider>
    )
}

I nostri due componenti possono ora accedere al contesto

Step 3: Aggiunta al carrello

Adesso dobbiamo modificare il componente AddToCartButton per utilizzare il metodo AddToCart() fornito dal contesto; per farlo dobbiamo includere il modello del contesto nel componente e tramite un hook useContext() importare il metodo

AddToCartButton.js

import { useContext } from 'react';
import { CartContext } from 'CartContext';

export default function AddToCartButton({product}) {
    const { addToCart } = useContext(CartContext);

    const addProduct = function () {
        addToCart(product);
    }

    return (
        <button onClick={addProduct}>
            Add to cart
        </button>
    )
}

Il pulsante è in grado di aggiornare lo stato del contesto, senza effettivamente esserne a conoscenza.

Step 4: mostrare la quantità

La stessa logica la applichiamo all'icona del carrello, in questo caso però non ci servirà il metodo del contesto, ma il valore che contiene i prodotti del carrello

CartIcon.js

import { useContext } from 'react';
import { CartContext } from 'CartContext';

export default function CartIcon() {
    const { cart } = useContext(CartContext);
    const qty = (cart && cart.length > 0) ? "(" + cart.length + ")" : "";

    return (
        <Link to="/cart">
            <Icon icon="cart"/>
            {qty}
        </Link>
    )
}

Con questo step si conclude l'esempio, al click sul pulsante in dettaglio prodotto corrisponderà l'aggiornamento dello stato del provider e quindi anche del valore presente sull'icona del carrello.

In effetti l'utilizzo delle context API non è complesso, si tratta di pochi e semplici passaggi. Ci sono situazioni in cui questa tecnologia può sostituire soluzioni più complesse come Redux o Apollo Client per la gestione di stati condivisi, ma si tratta sempre di situazioni basilari come ad esempio la gestione del tema o della lingua e non è una risposta applicabile a requisiti ad alta complessità.