Context

In een React applicatie wordt data van het bovenste component doorgegeven naar het onderste component aan de hand van props. Dit is mogelijk in een applicatie waar de component structuur niet heel diep is, maar naar mate de applicatie groeit, groeit meestal ook de diepte van je componenten structuur. Je zal dan vaak een prop verschillende niveau's diep moeten doorgeven.

Stel dat we een light theme en een dark theme willen ondersteunen in onze applicatie. Elk component heeft deze prop nodig want elk component moet zijn UI aanpassen als de theme light of dark is.

const App = () => {
    ...
    <ListPage games={games} handleAdd={handleAdd} theme={'light'}/>
    ....
}
interface ListPageProps {
  games: Game[],
  handleAdd: (game: Game) => void,
  theme: string
}

const ListPage = ({games,handleAdd, theme} : ListPageProps) => {
  return (
    <div>
      <List games={games} theme={theme}/>
      <InputView onAdd={handleAdd} theme={theme} />
    </div>
  );
}
interface ListProps {
  games: Game[],
  theme: string
}

const List = ({ games, theme }: ListProps) => {
  return (
    <div className={styles.gamesList}>{games.map((game: Game) => {
      return <ListItem game={game} theme={theme}/>
    })}
    </div>
  );
}
interface ListItemProps {
  game: Game,
  theme: string,
}

const ListItem = ({ game, theme }: ListItemProps) => {
  ...
}

Schematisch ziet dat er als volgt uit:

Je ziet dat zelfs voor een kleine applicatie, zoals als degene die we in deze cursus hebben gemaakt, dat deze theme prop al moeten doorgeven doorheen 3 niveaus van componenten. Bij grotere applicaties gaat dit nog veel erger worden. Daarom heeft React voor de Context API gezorgd. Dit is een manier om data te gaan delen doorheen componenten zonder door heel de structuur te moeten doorgegeven worden.

Het eerste wat we moeten doen is een Context aanmaken aan de hand van de createContext

export const ThemeContext = React.createContext({mode: 'light'});

Je moet altijd een default waarde meegeven aan de context. Deze default waarde wordt enkel gebruikt als er geen provider aanwezig is bovenaan in de component structuur. We geven hier een object met een mode property. Dus in ons geval zal onze layout dus standaard in light mode staan.

Nu moeten we een Provider maken van onze ThemeContext zodat we de values kunnen meegeven die we in de componenten willen gebruiken. De provider moet rond de bovenste component staan die onze context willen gebruiken.

return (
  <ThemeContext.Provider value={{mode: 'light'}}>
    <Router>
      ...
    </Router>
  </ThemeContext.Provider>

Nu kan je de values van de ThemeContext provider opvragen aan de hand van de useContext hook. Als de mode dark is dan voegen we een extra className toe zodat we in ons css bestand een gameCardDark class kunnen maken.

import { ThemeContext } from 'components/App/App';
import { useContext } from 'react';
import { Link } from 'react-router-dom';
import { Game } from 'types';
import styles from './List.module.css'

interface ListItemProps {
  game: Game
}

const ListItem = ({ game }: ListItemProps) => {
  const {mode} = useContext(ThemeContext);

  return (
    <Link to={`/detail/${game.id}`}>
      <div key={game.id} className={`${styles.gameCard} ${mode === 'dark' ? styles.gameCardDark : ''}`}>
        <div>{game.name}</div>
        <div>{game.releaseYear}</div>
        <div>{game.sales}</div>
      </div>
    </Link>
  )
}

export default ListItem;

Je ziet nu dat elk component toegang heeft tot de mode variabele die we in de context hebben aangeboden zonder dat we deze moeten doorgeven doorheen de component hierarchie.

We kunnen ook handler functies toevoegen in onze ThemeContext zodat we ook van elk component de mode kunnen aanpassen. We voegen nu aan de createContext functie een setMode functie toe. We moeten hier een default value voor aanbieden, op dit moment hebben we nog geen handler dus we geven een lege functie mee.

export const ThemeContext = React.createContext({mode: 'light', setMode: (mode: string) => {}});

We maken nu in onze App component een state aan die de mode bijhoudt zodat we deze kunnen aanpassen.

const [mode, setMode] = useState('light');

Nu geven we de setMode functie mee aan de provider

<ThemeContext.Provider value={{mode: mode, setMode: setMode}}>

Vanaf nu is de setMode functie overal beschikbaar in elk component die deze context gebruikt. We kunnen nu de Header component uitbreiden met een ModeButton component die gebruik kan maken van de mode en setMode van de context. We maken hier gebruik van een FontAwesome icon. Dit is een grote verzameling van icons die worden aangeboden om in projecten te gebruiken. Deze kan je installeren door

npm install --save @fortawesome/fontawesome-svg-core
npm install --save @fortawesome/free-solid-svg-icons
npm install --save @fortawesome/react-fontawesome

uit te voeren. We maken ook nog een nieuwe component ModeButton in een nieuwe component directory components/ModeButton

import React, { useContext } from 'react';

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons'
import { ThemeContext } from 'components/App/App';
import styles from './ModeButton.module.css'

const ModeButton = () => {
  const {mode, setMode} = useContext(ThemeContext);
  return (
    <div className={styles.modeToggle}>
      
      {mode === 'dark' ? 
        <button onClick={() => {setMode('light')}}><FontAwesomeIcon icon={faSun} className={styles.modeIcon}/></button> :
        <button onClick={() => {setMode('dark')}}><FontAwesomeIcon icon={faMoon} className={styles.modeIcon}/></button>
      }
    </div>
  )
}

export default ModeButton;

We tonen aan de hand van de huidige mode nu een button die een zon icon toont als het dark mode is en een maan icon als het light mode is. We zetten hier een onClick listener op en roepen in deze listener de setMode functie op. Dit is de setMode functie die we hebben meegegeven met de ThemeContext provider.

We kunnen de button stylen met een css module te maken voor de ModeButton component.

.modeToggle {
  display: flex;
  align-items: center;
  margin-left: auto;
  padding-right: 20px;
}

.modeToggle button {
  background-color: transparent;
  border: none;
}

.modeIcon {
  color: white
}

.modeIcon:hover {
  color: lightblue
}

Nu kunnen we deze in de Header component plaatsen en dan hebben we een manier om de dark mode en de light mode te triggeren.

Er zijn nog veel andere voorbeelden waar Context handig is om je applicatie structuur te verbeteren. Maar zorg er wel voor dat je deze niet te veel begint te gebruiken. Zolang je props niet op veel verschillende plaatsen moeten gebruikt worden of niet door verschillende componenten heen moeten doorgegeven worden is het soms beter om toch props te blijven gebruiken. Context is geen alternatief voor props, dus gebruik het zeker niet om alles globaal beschikbaar te maken.

Last updated

Was this helpful?