3

Zobrazování seznamů

Ukážeme si, jak v React komponentách zobrazovat seznamy.

Naše cesta Reactem pokračuje k další z velmi důležitých dovedností. Téměř každá webová aplikace zobrazuje nějaký seznam, ať už to je nákupní košík, seznam divadelních představení, jídelní lístek nebo třeba seznam nepřečtených e-mailů. V této lekci si ukážeme jak v Reactu zařídit zobrazení seznamu uloženého v nějakém JavaScriptovém poli.

Opakování metody map

Na zobrazování seznamů budeme v Reactu používat metodu map, kterou již znáte z kapitoly o pokročílém JavaScriptu. Jistě se nám vyplatí si tuto metodu připomenout a zopakovat.

Metoda map slouží k tomu, abychom z jednoho JavaScriptového pole vyrobili pole jiné pomocí nějaké tranformační funkce. Takto například vyrobíme e-maily pro seznam uživatelů.

const names = ['petr', 'jana', 'marek', 'eva', 'lenka', 'ondra'];
names.map((name) => {
  return `${name}@mejlik.cz`;
});

Jistě si také vzpomenete, že jsme při zápisu takovýchto transformací používali zkrácený zápis arrow funkcí.

const names = ['petr', 'jana', 'marek', 'eva', 'lenka', 'ondra'];
names.map((name) => `${name}@mejlik.cz`);

Zkrácený zápis se závorkami

Občas se nám stane, že funkce sice nedělá nic jiného, než že vrací hodnotu, ale tato hodnota se nevejde na jeden řádek. Příkladem budiž například Reactová komponenta.

const Time = (props) => {
  return (
    <div className="time">
      <span className="time__hours">{props.hours}</span>
      :
      <span className="time__mins">{props.minutes}</span>
    </div>
  );
}

Tuto komponent nedělá nic jiného, než že vrací JSX element. Můžeme ji proto převést na zkrácený zápis. Sluší se ale v zápise ponechat kulaté závorky pro lepší čítelnost.

const Time = (props) => (
  <div className="time">
    <span className="time__hours">{props.hours}</span>
    :
    <span className="time__mins">{props.minutes}</span>
  </div>
);

Pokud je komponenta malá, klidně se bez závorek obejdeme.

const User = (username) => <div className="user__name">{username}</div>;

Všimněte si, že pomocí takovéto komponenty bychom například mohli vyrobit pole JSX elementů z našeho pole uživatelů.

const names = ['petr', 'jana', 'marek', 'eva', 'lenka', 'ondra'];
names.map((name) => <User username={name} />);

Z tohoto bodu zbývá už jen malý krůček, abychom takovéto pole JSX komponent dokázali zobrazit na naší stránce. To si však necháme až na druhou část lekce.

Cvičení: Zkracování a map

1

Zkracovací jednohubky

pohodička

Založte si obyčejnou stránku s JavaScriptem, bez Webpacku i bez Reactu. Následující funkce přepište do zkráceného zápisu. Vlastními slovy popište, k čemu nejspíše funkce slouží.

  1. const isEmail = (str) => {
      return str.includes('@');
    };
    
  2. const roll = () => {
      return Math.floor(Math.random() * 6) + 1;
    };
    
  3. const getNumber = (id) => {
      return Number(document.querySelector(`#${id}`).value);
    };
    
  4. const weather = (temperature) => {
      if (temperature > 16) {
        return 'teplo';
      }
    
      return 'zima';
    };
    

    Nápověda: Použijte ternární operátor.

2

Opakování map

to dáš

Založte si obyčejnou stránku s JavaScriptem, bez Webpacku i bez Reactu. Do souboru index.js si zkopírujte následující pole.

const weekdays = [
  'pondělí',
  'úterý',
  'středa',
  'čtvrtek',
  'pátek',
  'sobota',
  'neděle',
];
const months = [
  {
    name: 'leden',
    days: 31,
  },
  {
    name: 'únor',
    days: 28,
  },
  {
    name: 'březen',
    days: 31,
  },
  {
    name: 'duben',
    days: 30,
  },
  {
    name: 'květen',
    days: 31,
  },
  {
    name: 'červen',
    days: 30,
  },
  {
    name: 'červenec',
    days: 31,
  },
  {
    name: 'srpen',
    days: 31,
  },
  {
    name: 'září',
    days: 30,
  },
  {
    name: 'říjen',
    days: 31,
  },
  {
    name: 'listopad',
    days: 30,
  },
  {
    name: 'prosinec',
    days: 31,
  },
];

Všechny body níže vyřešte pomocí metody map. Tam, kde je to možné, použijte zkrácený zápis arrow funkcí. Výsledky vypisujte do konzole pomocí console.log.

  1. Z pole weekdays vyrobte pole obsahující všechny názvy dnů napsané VELKÝMI PÍSMENY.

  2. Z pole weekdays vyrobte pole obsahující pouze první dvě písmena každého dne, tedy

    ['po', 'út', 'st' /* atd. */];
    
  3. Z pole months vyrobte pole obsahující pouze počty dní v každém měsíci.

  4. Z pole months vyrobte pole obsahující pro každý měsíc datum jeho prvního dne v roce 2020, tedy

    ['1. leden 2020', '1. únor 2020' /* atd. */];
    

Seznamy v JSX

Na konci předchozí části jsme se dotknuli způsobu, jak v Reactu zobrazit nějaký seznam. Celá myšlenka tkví v tom, že React nám velmi přimočaře umožňuje zobrazit pole JSX elementů.

Představme si následující pole obsahující li elementy.

const dayElements = [
  <li>pondělí</li>,
  <li>úterý</li>,
  <li>středa</li>,
  <li>čtvrtek</li>,
  <li>pátek</li>,
];

Všimněte si, že jde o normální JavaScriptové pole, které jako svoje prvky obsahuje JSX elementy. Každý JSX element je hodnota, není tedy žádný problém mít pole takovýchto hodnot.

Pokud takové pole máme už připravené, můžeme elementy v něm obsažené zobrazit uvnitř nějakého rodiče prostě tak, že proměnnou dayElements do tohoto rodiče vložíme.

const App = () => (
  <>
    <h1>Pracovní dny</h1>
    <ol className="days">{dayElements}</ol>
  </>
);

Kdyby byl v proměnné dayElements uložen řetězec, budeme mít uprostřed ol seznamu prostě kousek textu. Jelikož však máme v dayElements pole JSX elementů, React je jednoduše zapojí jako děti našeho seznamu.

Když tento kód spustíme, React nám do konzole prohlížeče vypíše varování.

Warning: Each child in a list should have a unique "key" prop.

Pro tutu chvíli jej můžeme ignorovat. Později si vysvětlíme co přesně znamená a jak se k němu postavit.

Použití map

V předchozím případě jsme pole JSX elementů měli připravené dopředu. V praxi jej však chceme vyrobit z nějakých dat. Máme například názvy dní v týdnu jako řetězce v poli.

const days = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek'];

Z tohoto pole chceme vyrobit pole JSX elementů pro náš seznam. K tomu nám stačí poučít funkci map, kterou jsme v první části lekce tak poctivě trénovali.

const days = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek'];

const dayElements = days.map((day) => <li>{day}</li>);

const App = () => (
  <>
    <h1>Pracovní dny</h1>
    <ol className="days">{dayElements}</ol>
  </>
);

Tento kód bude hezky fungovat. Z hlediska profesionáních vývojářů je však zbytečně ukecaný. Proměnnou dayElements používáme pouze jednou, takže můžeme na místě jejího použití rovnou zavolat náš mapovací kód.

const days = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek'];

const App = () => (
  <>
    <h1>Pracovní dny</h1>
    <ol className="days">
      {
        days.map((day) => <li>{day}</li>)
      }
    </ol>
  </>
);

Pokud vám kód výše stále přijde srozumitelný, je zde příležitost jej udělat ještě malinko kompaktnější. V praxi se často setkáte s takovýmo formátováním.

const days = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek'];

const App = () => (
  <>
    <h1>Pracovní dny</h1>
    <ol className="days">
      {days.map((day) => (
        <li>{day}</li>
      ))}
    </ol>
  </>
);

Tady už je potřeba si dát zatraceně dobrý pozor na jednotlivé závorky a dobře se orientovat v tom, co která znamená.

Složitější komponenty

Z praktického hlediska je náš ilustrativní příklad výše stále hodně jednoduchý. V praxi míváme mnohem obsáhlejší data i mnohem složitejší komponenty. Vezměme například data pro nám již známý nákupní seznam.

const list = [
  { product: 'mrkev', amount: '3ks', bought: false },
  { product: 'paprika', amount: '2ks', bought: true },
  { product: 'cibule', amount: '2ks', bought: false },
  { product: 'čínské zelí', amount: '1ks', bought: true },
  { product: 'arašídy', amount: '250g', bought: false },
  { product: 'sojová omáčka', amount: '1ks', bought: false },
];

Pokud tento seznam budeme chtít zobrazit, budeme potřebovat složitější JSX strukturu. Například jako v následující ukázce.

const App = () => (
  <>
    <h1>Nákupní seznam</h1>
    <div className="shopping-list">
      {list.map((item) => {
        const itemClass = item.bought ? 'item item--selected' : 'item';
        return (
          <div className={itemClass}>
            <span className="item__product">{item.product}</span>
            <span className="item__amount">{item.amount}</span>
          </div>
        );
      })}
    </div>
  </>
);

Všimněte si, že funkce, která vyrábí JSX pro jednotlivé položky našeho seznamu, je už o kus obsáhlejší. Dokonce o tolik, že už nejde napsat zkráceným způsobem, a musíme použít složené závorky a return. Tato ukázka opět poslouží jako dobré cvičení na pozornost ohledně závorek.

V praxi však často nastane situace, že funkce použitá uvnitř map je tak složitá, že je těžké se v kódu orientovat. Náš poslední příklad už je také trochu na hraně. V takovém případě se nám rozhodně vyplatí vytvořit si pro zobrazování jednotlivých prvků seznamu komponentu, například takto.

const ShoppingItem = (props) => {
  const itemClass = props.bought ? 'item item-selected' : 'item';
  return (
    <div className={itemClass}>
      <span className="item__product">{props.product}</span>
      <span className="item__amount">{props.amount}</span>
    </div>
  );
};

const App = () => (
  <>
    <h1>Nákupní seznam</h1>
    <div className="shopping-list">
      {list.map((item) => (
        <ShoppingList
          product={item.product}
          amount={item.amount}
          bought={item.bought}
        />
      ))}
    </div>
  </>
);

Takto je náš kód mnohem čitelnější. Toto je jeden z hlavních důvodů, proč rozdělujeme naše aplikace na komponenty. Lépe se tak dokážeme v kódu orientovat nejen my, ale také naši kolegové, kteří pracují na stejném projektu a musí číst náš kód.

Práce s key prop

U příkladů výše jsme při zobrazování seznamů narazili na toto varování v konzoli

Warning: Each child in a list should have a unique "key" prop.

Tím se nám React snaží říct, že u každé komponenty v seznamu potřebuje mít unikátní klíč, který tuto položku identifikuje. Jde o podobný princip jako je například id u HTML elementů. Díky unikátním klíčům dokáže React rychleji vyřešit změny uvnitř seznamu jako přidávání nebo mazání položek.

Nejjednodušší situaci máme ve chvíli, kdy jsou naše data připravená tak, že každá položka obsahuje vlastnost s unikátními hodnotami. Podívejme se například na náš nákupní seznam.

const list = [
  { product: 'mrkev', amount: '3ks', bought: false },
  { product: 'paprika', amount: '2ks', bought: true },
  { product: 'cibule', amount: '2ks', bought: false },
  { product: 'čínské zelí', amount: '1ks', bought: true },
  { product: 'arašídy', amount: '250g', bought: false },
  { product: 'sojová omáčka', amount: '1ks', bought: false },
];

Všechny hodnoty vlastnoti product jsou unikátní. Můžeme tedy tuto vlastnost použít jako klíč.

<div className="shopping-list">
  {list.map((item) => (
    <ShoppingList
      key={item.product}
      product={item.product}
      amount={item.amount}
      bought={item.bought}
    />
  ))}
</div>

Všimněte si, že například vlastnost amount unikátní hodnoty nemá. Nelze tedy použít jako klíč. Pokud to uděláte, React si bude v konzoli opět hlasitě stěžovat.

V praxi občas narazíme na data, která žádnou unikátní vlastnost použitelnou jako klíč nemají. V takovém případě čeká trošku bolehlav i zkušenější programátory. Řešení tětcho situací proto necháme na některou z pozdějších lekci, až budete v Reactu více kovaní.

Cvičení: Zobrazování seznamů

3

Česká města

to dáš
  1. Založte si React projekt podle již známého postupu.
  2. Do souboru index.jsx vložte mimo komponentu App pole s názvy deseti největších českých měst.
    const cities = [
      'Praha',
      'Brno',
      'Ostrava',
      'Plzeň',
      'Liberec',
      'Olomouc',
      'České Budějovice',
      'Hradec Králové',
      'Ústí nad Labem',
      'Pardubice',
    ];
    
  3. Stále mimo hlavní komponentu vezměte pole cities a pomocí funkce map z něj vyrobte pole JSX elementů. Každý JSX element nechť má následující strukturu.
    <div className="city">Název města</div>
    
    Výsledné pole uložte do proměnné cityElements.
  4. Použijte pole cityElements uvnitř komponenty App a zobrazte jej tak na vaší stránce.
  5. Zbavte se proměnné cityElements a funkci map použijte přímo uvnitř komponenty App.
  6. V konzoli si React bude stěžovat, že mu chybí key prop. Máme však štěstí, jména měst jsou unkátní. Můžeme tak na náš div přídat prop key a do něj poslat přímo název města.
4

Česká města 2

to dáš

Pokračujte v projektu z předchozího příkladu.

  1. Do pole cities si uložte obsáhlejší data o českých městech.
    const cities = [
      {
        name: 'Praha',
        population: 1324277,
        area: 496.21,
      },
      {
        name: 'Brno',
        population: 381346,
        area: 230.18,
      },
      {
        name: 'Ostrava',
        population: 287968,
        area: 214.23,
      },
      {
        name: 'Plzeň',
        population: 174842,
        area: 137.67,
      },
      {
        name: 'Liberec',
        population: 104802,
        area: 106.09,
      },
      {
        name: 'Olomouc',
        population: 100663,
        area: 103.33,
      },
      {
        name: 'České Budějovice',
        population: 94463,
        area: 55.6,
      },
      {
        name: 'Hradec Králové',
        population: 92939,
        area: 105.69,
      },
      {
        name: 'Ústí nad Labem',
        population: 92716,
        area: 93.97,
      },
      {
        name: 'Pardubice',
        population: 91727,
        area: 82.66,
      },
    ];
    
  2. Upravte kód vaší aplikace tak, aby u každého města zobrazoval kromě názvu také počet obyvatel a rozlohu v kilometrech.
  3. Jako key prop opět použijte název města.
  4. Vytvořte komponentu City, jejímž úkolem bude zobrazovat jedno město. Tato kompnenta bude mít props name, population a area. Použijte komponentu k zobrazení každého města ze seznamu.
  5. Pro komponentu City vytvořte vlastní složku a komponentu malinko nastylujte, aby vypadala hezky.

Doporučené úložky na doma

5

Česká města 3

zapni hlavu

Pokračujte v projektu z předchozího příkladu.

  1. Stáhněte si ZIP soubor, který obsahuje seznam všech českých měst aktuální k 1. lednu 2020. Soubor cz-cities.js vložte do složky src vašeho projektu.
  2. Smažte pole cities ve vašem index.jsx a místo něj si imporujte pole cities ze souboru cz-cities.js. Už v tuto chvíli by měl váš projekt fungovat jako v přechozím příkladu. Pouze na stránce uvidíte mnohem více měst než před tím.
  3. Upravte komponentu City tak, aby zobrazovala také okres, do kterého dané město patří.
  4. U každého města je odkaz na fotografii z daného města. Do vaší komponenty City přidejte element img a zobrazte u kažého města také jeho obrázek. Opět si můžete malinko pohrát se styly, aby stránka vypadala hezky.
6

Podcasty

to dáš
  1. Stáhněte si nastylovanou stránku zobrazující epizody jakéhosi podcastu.

  2. Známým postupem si založte nový React projekt.

  3. Vytvořte komponentu Episode, která bude zobrazovat jednu podcastovou epizodu. Její props budou num, title a guest. Pomocí této komponenty zobrazte stejné dvě epizody, jako vidíte na připravené stránce. Z připravné stránky vykradněte CSS styly a zařiďte aby vaše aplikace vypadala stejně.

  4. Nezapomeňte pro vaši komponentu vytvořit separátní složku s vlastním JavaScriptem a CSS styly.

  5. Místo natvrdo zadrátovaných epizod přímo v kódu použijte následujicí data a na stránce zobrazte všechny uvedené epizody.

    const episodes = [
      { num: 126, title: 'Robot, který snědl koblihu', guest: 'Radovan Siwek' },
      { num: 127, title: 'Hledání plyšového Yettiho', guest: 'Vojtěch Ryba' },
      { num: 128, title: 'Moderní hrachová polévka', guest: 'Kamila Štancová' },
      { num: 129, title: 'Poloautomatické zrcadlo', guest: 'Janka Janovská' },
      { num: 130, title: 'Máčené hlavy parlamentu', guest: 'Jonáš Daněk' },
      { num: 131, title: 'Služby na hraně přívěsu', guest: 'Tereza Křivánková' },
      { num: 132, title: 'Klasifikace sněžných klokanů', guest: 'Josef Stix' },
      { num: 133, title: 'Rybolov v Oceánu bouří', guest: 'Ludmila Gajová' },
    ];
    
  6. Rozmyslete si, která vlastnost se nejlépe hodí jako klíč a použijte ji, aby React přestal v konzoli prudit, že mu chybí key prop.