Aller au contenu

useContext

Une façon de passer des données d'un élément parent vers un élément enfant est via les props. Le flux normal de données dans React est de haut en bas. Les éléments en bas ne peuvent pas influencer les données venant de plus haut.

Lorsque c'est requis pour un élément enfant d'influencer les données venant d'un parent, React offre une fonctionnalité qui se nomme contexte. Le contexte rend possible la mise à jour d'états par tous les sous-éléments selon les besoins.

Prenons l'exemple suivant :

graph TD
  A[Home] --> B[Panier];
  A[Home] --> C[Fiche];
  B[Panier] --> D[Fiche];

L'élément Home a un panier contenant les items désirés par l'utilisateur. L'élément Fiche représente un item avec une photo, une description et un prix. L'élément Panier contient les fiches des items ajoutés par l'utilisateur.

Si nous voulons que le bouton Ajouter au panier de l'élément Fiche puisse influencer le contenu du Panier, il faut créer un contexte :

graph TD
  Z[ContextePanier] --> A[Home];
  A[Home] --> B[Panier];
  A[Home] --> C[Fiche];
  B[Panier] --> D[Fiche];

Tous les éléments sous ContextePanier peuvent accèder au contenu et le modifier au besoin. Dans cette situation, la liste des items au panier vient du contexte et est utilisé dans l'élément Panier alors que Fiche s'ajoute ou se retire du panier via le contexte.

Démo de useContext

Voici les éléments pertinents pour l'utilisation de contexte :

panier.context.tsx
import React, { useState } from 'react';

interface IItemPanier {
  id: number;
  nom: string;
  photo: string;
  prix: number;
  quantite: number;
}

export type PanierContextType = {
  itemsPanier: IItemPanier[];
  panierOuvert: boolean;
  setItemsPanier: (itemsPanier: IItemPanier[]) => void;
  setPanierOuvert: (ouvert: boolean) => void;
};

const panierVide: IItemPanier[] = [];

export const PanierContext = React.createContext<PanierContextType>({
  itemsPanier: panierVide,
  panierOuvert: false,
  setItemsPanier: () => {},
  setPanierOuvert: () => {},
});

export default function PanierProvider(props: any) {
  const [itemsPanier, setItemsPanier] = useState(panierVide);
  const [panierOuvert, setPanierOuvert] = useState(false);

  const values = {
    itemsPanier,
    panierOuvert,
    setItemsPanier,
    setPanierOuvert,
  };
  return (
    <PanierContext.Provider value={values}>
      {props.children}
    </PanierContext.Provider>
  );
}
App.tsx
import './App.css';
import PanierProvider from './contexts/panier.context';
import Home from './components/home.component';

function App() {
  return (
    <PanierProvider>
      <Home />
    </PanierProvider>
  );
}

export default App;
panier.component.tsx
import { useContext } from 'react';
import { Drawer } from '@mui/material';
import { PanierContext } from '../contexts/panier.context';
import Fiche from './fiche.component';
import { Box } from '@mui/material';

export default function Panier() {
  const { itemsPanier, panierOuvert, setPanierOuvert } =
    useContext(PanierContext);
  return (
    <Drawer
      anchor="right"
      open={panierOuvert}
      onClose={() => {
        setPanierOuvert(false);
      }}
    >
      <Box sx={{ width: 300 }}>
        {itemsPanier &&
          itemsPanier.map((item) => {
            return <Fiche chapeau={item} dansPanier={true} />;
          })}
      </Box>
    </Drawer>
  );
}
fiche.component.tsx
import { useContext } from 'react';
import { Card } from '@mui/material';
import { CardActions } from '@mui/material';
import { CardContent } from '@mui/material';
import { CardMedia } from '@mui/material';
import { Button } from '@mui/material';
import { Typography } from '@mui/material';
import { PanierContext } from '../contexts/panier.context';
import { IChapeau } from '../models/ichapeau.model';

interface IFiche {
  chapeau: IChapeau;
  dansPanier: boolean;
}

export default function Fiche(props: IFiche) {
  const { itemsPanier, setItemsPanier } = useContext(PanierContext);

  const ajouterAuPanier = () => {
    const nouveauPanier = [...itemsPanier, { ...props.chapeau, quantite: 1 }];
    console.log(nouveauPanier);
    setItemsPanier(nouveauPanier);
  };

  const retirerDuPanier = () => {
    var i = 0;
    console.log('retirer du panier : ', props.chapeau.id);
    while (i < itemsPanier.length) {
      if (itemsPanier[i].id === props.chapeau.id) {
        itemsPanier.splice(i, 1);
      } else {
        ++i;
      }
    }
    const nouveauPanier = [...itemsPanier];
    setItemsPanier(nouveauPanier);
  };

  return (
    <Card sx={{ width: 300, maxWidth: 300, height: 300, maxHeight: 300 }}>
      <CardMedia
        component="img"
        height="150"
        sx={{ objectFit: 'contain' }}
        image={props.chapeau.photo}
      />
      <CardContent>
        <Typography gutterBottom variant="h6" component="div">
          {props.chapeau.nom}
        </Typography>
        <Typography variant="body2" color="text.secondary">
          {props.chapeau.prix}&nbsp;$
        </Typography>
      </CardContent>
      <CardActions>
        {!props.dansPanier && (
          <Button
            size="small"
            color="primary"
            onClick={() => ajouterAuPanier()}
          >
            Ajouter au panier
          </Button>
        )}
        {props.dansPanier && (
          <Button
            size="small"
            color="primary"
            onClick={() => retirerDuPanier()}
          >
            Retirer du panier
          </Button>
        )}
      </CardActions>
    </Card>
  );
}

Se connecter à un API

Il est préférable d’utiliser la librairie Axios pour aller chercher vos données de l’API :

fetch_bieres.ts
axios.get('https://bieres.profinfo.ca/api/bieres').then((response) => {
  setListeBieres(response.data.bieres);
});