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 Rectu 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 jste si již vyzkoušeli v předchozí kapitole. Určitě se nám však 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`;
});

Zkracování zápisu funkcí

Už na ternárním operátoru z minulé kapitoly jsme si ukázali, že programátoři se snaží kód zkracovat kde to jen jde, aby dokázali programy psát co nejrychleji. Pro arrow funkce proto existuje speciální zkrácený zápis. Jako příklad vězměme naší funkci pro vytváření e-mailu. Její nezkrácený zápis vypadá takto.

(name) => {
  return `${name}@mejlik.cz`;
};

Pokud však funkce nedělá nic jiného, než že rovnou vrací nějako hodnotu, můžeme její zápis zkrátit tím, že vynecháme složené závorky a slovíčko return.

(name) => `${name}@mejlik.cz`;

Díky tomu můžeme náš kód pro generování e-mailů zapsat takto.

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 však 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

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 Searchbox = (props) => {
      return `
        <div className="searchbox">
          <label>
            ${props.label}
            <input type="text" />
          </label>
        </div>
      `;
    };
    
  5. 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: 'červenc',
    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ávený zápis arrow funkcí.

  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. */];
    
  5. Z pole months vyrobte pole řetězců obsahujcí HTML dle následujícího vzoru.

    [
      '<span class="month">leden</span>',
      '<span class="month">únor</span>',
      /* atd. */
    ];
    

Seznamy v JSX

Na konci předchozí části už 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, takže není žádný problém mít pole takovýchto hodnot.

Pokud takové pole máme, můžeme tyto elementy zobrazit uvnitř nějakého rodiče prostě tak, že proměnnou dayElements do tohoto rodiče prostě 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 = [
  { name: 'mrkev', amount: '3ks', bought: false },
  { name: 'paprika', amount: '2ks', bought: true },
  { name: 'cibule', amount: '2ks', bought: false },
  { name: 'čínské zelí', amount: '1ks', bought: true },
  { name: 'arašídy', amount: '250g', bought: false },
  { name: '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í sezname</h1>
    <div className="shopping-list">
      {list.map((item) => {
        const itemClass = item.bought ? 'item item--selected' : 'item';
        return (
          <div className={itemClass}>
            <span className="item__name">{item.name}</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 ja 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 už je tak složítá, že je těžké se v kódu zorientovat. 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__name">{props.name}</span>
      <span className="item__amount">{props.amount}</span>
    </div>
  );
};

const App = () => (
  <>
    <h1>Nákupní sezname</h1>
    <div className="shopping-list">
      {list.map((item) => (
        <ShoppingList
          name={item.name}
          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ší kolegové, kteří pracují na stejném projektu a musí číst náš kód.

Cvičení: Zobrazování seznamů

3

Česká města

to dáš

Založte si React projekt podle již známého project starteru.

  1. 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',
    ];
    
  2. 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.
  3. Použije pole cityElements uvnitř komponenty App v zobrazte jej na vaší stránce.
  4. Zbavte se proměnné cityElements a funkci map použijte přímo uvnitř komponenty App.
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. 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.
  4. 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 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. Založte si React projekt podle již známého project starteru.

  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á' },
    ];