Skip to content
Kezdőlap » Kereső készítés, state – ReactJS gyorstalpaló 5.

Kereső készítés, state – ReactJS gyorstalpaló 5.

Mit ér egy weboldal kereső nélkül? A ReactJS gyorstalpaló ezen leckéjében kereső készítésre vállalkozunk, amihez state-eket fogunk használni.

App komponens létrehozása

Mielőtt még nekilátunk a kereső komponens elkészítésének, hogy pofásabb legyen az alkalmazásunk, adunk neki valamilyen címet. Az egészet középre igazítjuk és megcsináljuk a kereső mezőt is.

De először is csinálunk egy App komponenst, mert úgy szép, ha az index.js-ben ezt adjuk át a render-nek. És azt akarjuk elérni egyúttal, hogy az App komponens legyen minden más komponens szülője.

Tehát az első lépés, hogy az src mappában létrehozom, aztán megírom a kódját.

import React from "react";
 
const App = () => {
    return(
 
    );
}
 
export default App;

Azt szeretnénk, hogy az App komponens adja vissza a TerminatorList komponenst. Azt már jól tudjuk, hogy valamilyen elemnek kell keretbe fognia a komponenst a return-ön belül, úgyhogy én egy div-be teszem:

return (
    <div>
      <TerminatorList models={models} />
    </div>
  );

Ahhoz, hogy a TerminatorList komponenst lássa az App, elérhetővé kell tenni számára, ezért beimportálom. És persze kell neki a models is, ezt is importálom:

import TerminatorList from "./components/terminator-list/terminator-list.component";
import { models } from “./models.js”;

Végül az index.js-be beimportálom az App komponenst és azt renderelem ki:

import App from "./App";
 
ReactDOM.render(<App />, document.getElementById("root"));

Ha a böngészőben hiba nélkül megjelenik minden és sem a terminálban, sem a konzolban nincs hiba, akkor jók vagyunk.

Címsor létrehozás

Jöhet egy címsor hozzáadása, nem elfelejtve, hogy az egésznek kell valami konténer div:

const App = () => {
  return (
    <div>
      <h1>Terminator modellek</h1>
      <TerminatorList models={models} />
    </div>
  );
};

Kereső létrehozás

A címsor alatt fog megjelenni egy kereső dobozka, amit egy önálló komponensként hozok létre. Először elhelyezem a komponens hívását annak ellenére, hogy még nem is létezik:

<h1>Terminator modellek</h1>
<SearchBox />

Aztán létrehozok neki egy saját searchbox nevű könyvtárat a components mappa alatt és magát a komponens fájlt is elkészítem searchbox.component.jsx néven:

kereso-keszites-state-reactjs-gyorstalpalo-5-kep1

Jöhet a komponens (searchbox.component.jsx) váza:

import React from "react";
 
const SearchBox = () => {
    return(
 
    );
}
 
export default SearchBox;

Aztán még mielőtt elfelejteném, be is importálom az App.js fájlomba:

import SearchBox from "./components/searchbox/searchbox.component";

A komponens tuti biztos, hogy vissza fog adni egy html input elemet, ahová szöveget lehet beírni:

<input type='search' placeholder='model keresés...' />

A weboldalon pedig meg is kell jelennie a keresőnek. Persze még nem működik, de írni már lehet bele:

kereso-keszites-state-reactjs-gyorstalpalo-5-kep2

Mivel rend a lelke mindennek, a html-ben is struktúráljuk az elemeket. Így a kereső dobozt is körbevesszük egy div-el. Így könnyebb is lesz formázni. Adok hozzá néhány tachyons class nevet is:

    <div className="pa2">
      <input
        className="pa3 ba b--green bg-lightest-blue"
        type="search"
        placeholder="model keresés..."
      />
    </div>

Már jobban néz ki böngészőben:

kereso-keszites-state-reactjs-gyorstalpalo-5-kep3

Az egész oldalt középre is rendezhetjük. Ezt az App komponensen belül (App.js) teszem meg úgy,  hogy a külső div-nek adok egy className tulajsonságot, ebben pedig egy tachyon class-t:

const App = () => {
  return (
    <div className="tc">

Ezzel középre is lett rendezve minden:

kereso-keszites-state-reactjs-gyorstalpalo-5-kep4

Egyirányú adatfolyam

A következő nagy feladat annak megoldása, hogy a kereső az ne csak ott fityegjen, hanem működjön is. Most nincs itt Uri Geller aki azt mondaná, hogy “múúkodj!”.

kereso-keszites-state-reactjs-gyorstalpalo-5 Uri Geller

Helyette ezt nekünk kell megcsinálni.

Ismét gondoljuk át az egészet. Van eddig négy komponensünk. Az App, a SearchBox, a TerminatorList és végül a Terminator.

A keresőnek beszélgetnie kell a terminátorokat listázó komponenssel. Meg kell neki tudnia mondani, hogy hány terminátort jelenítsen meg. Ugyanakkor a TerminatorList komponensnek észre kell vennie, ha a keresőben valami változik.

Ez az ábra a komponenseket szemlélteti:

kereso-keszites-state-reactjs-gyorstalpalo-5-kep5

Ahogy már tanultuk, a React-ben egyirányú adatfolyam működik. Vagyis az adatok, információk felülről csorognak lefelé az egyes komponensekhez és nincsen össze-vissza ugrándozás. Tehát a SearchBox nem tud közvetlenül átkiabálni a TerminatorList komponensnek, hogy “jelenítsd meg ezt vagy azt”. Helyette az App az, amin keresztül kommunikálnak, mert mindkét komponensnek az App a szülője. Hogyan lehetséges ez?

State használata (elméletben)

A fentiekre a válasz a state. A state egy beépített react objektum. A state objektum az, ahol tárolni tudjuk a komponens különböző jellemzőit. Gyakorlatilag ezek a jellemzők a komponens aktuális állapotát jelentik. Amikor a state objektum megváltozik, akkor a React a komponenst újrarajzolja a képernyőn, vagyis újra rendereli.

Eddig már használtunk valami hasonlót, amit props-nak hívunk. Ezek sosem változnak. Ezeket a React csak olvasni tudja. Ezek mindig csak inputok egy komponens életében. Arra jó egy ilyen prop vagy tulajdonság, hogy a komponens megkapja, aztán utána csinál valamit ez alapján.

Ha megnézzük a TerminatorList komponenst, akkor az annyit csinál, hogy megkapja a megjelenítendő modeleket, és megjeleníti őket. És ez a komponens ezt csinálja folyamatosan, míg világ a világ. Adatok kap, megjelenít. Az ilyen funkciójú függvényeket tiszta azaz pure függvényeknek nevezünk. A React-ben ezeket tiszta komponenseknek hívjuk.

Csakhogy most szegény TerminatorList komponens életét más is megbolygatja. Ugyanis az alapján kell megjelenítenie a modeleket, hogy mi van a kereső dobozban. Ez pedig változhat. Tehát a searchbox-ban nem használhatjuk a props-ot erre. Ehhez valami más kell. A state pedig változhat. Ezt fogjuk felhasználni.

Ha a fenti ábrához visszatérünk, akkor úgy magyarázhatjuk, hogy egy szülő komponens biztosítja a state-et a gyerek komponensek számára. Az App majd megmondja a TerminatorList komponensnek a kereső tartalma alapján, hogy mennyi Terminator-t jelenítsen meg. Azt a TerminatorList, mint props kapja meg.

State használata (a gyakorlatban)

A sok beszéd helyett szerintem kezdjük el a state használatát. Ahhoz, hogy képes legyen a komponens state-eket használni, vagy class kell vagy hook. Ahogy mondtam egyszer, eleinte class komponenseket használunk. Mivel az App minden más komponensnek a szülője, célszerű ott kezelni a state-eket. Az App-ból lesz egy class komponens tehát:

class App extends React.Component {
  render() {
    return (
      <div className="tc">
        <h1>Terminator modellek</h1>
        <SearchBox />
        <TerminatorList models={models} />
      </div>
    );
  }
}

Ahhoz, hogy class komponensben state-et tudjunk használni, meg kell hívni az osztályunk constructor() metódusát, majd ezen belül a szülő osztály super() metódusát. Csak ez után tudjuk megadni a state-et, amit this-szel kezdünk:

class App extends React.Component {
  constructor() {
    super();
    this.state = {};
  }

Mit adunk meg a state objektumban? Egyrészt kellenek a megjelenítendő modelek, másrészt ismernünk kell a keresőmező tartalmát is, ami kezdetben üres:

this.state = {
        models: models,
        searchfield: ''
};

Most, hogy a state-et hozzáadtuk az alkalmazáshoz, már van valamink, ami vezérelni tudja majd a komponensek megjelenítését.

Eddig a listázó komponens így érte el a listázandó modelleket:

<TerminatorList models={models} />

Most viszont már a state-ből fogja ezt venni:

<TerminatorList models={this.state.models} />

Igazából a listázó működése semmit sem fog változni, mert props-ként ugyanúgy megkapja a megjelenítendő modelleket.

Az App komponens feladatai ugyanakkor kibővülnek, mert le kell tudnia kezelni, ha megváltozik a kereső mező tartalma. Az App komponensen belül létrehozunk egy tetszőlegesen elnevezhető függvényt erre. Ezzel a függvénnyel mondja meg az App a React-nek, hogy valahányszor megváltozik a kereső tartalma, mást kell megjeleníteni. A függvény egy ún. esemény objektumot kap meg paraméterül, amit a megértés kedvéért most egyszerűen csak kiíratunk a konzolba:

onSearchChange = (event) => {
      console.log(event);
 }

Osztályon belüli eseménykezelők és a this

Nagyon-nagyon fontos szabály, hogy ha osztályon belül saját függvényt / metódust írunk, akkor ha ezen a függvényen belül használni akarjuk a this szót, ami magára a class-ra hivatkozik, akkor nyíl függvényt hozzunk létre!

Na és ezzel végül is írtunk egy hasonló eseménykezelőt, mint amiket JS-ben szoktunk. Értelemszerűen ezt is hozzá kell rendelni valamihez, ami most nem más, mint a SearchBox komponens. A SearchBox-nak kitalálok valami tulajdonság nevet és ebben adom meg neki az eseménykezelőt:

  <SearchBox searchChange={this.onSearchChange} />

Ezt persze a kereső komponensben is le kell kezelni. A SearchBox komponens props-ként megkapja az eseménykezelőt, amit destruktúrálással kiszedünk a teljes props-ból. Aztán beállítunk egy onChange tulajdonságot az input-nak, csakúgy mint a normál html inputoknál:

const SearchBox = ({ searchChange }) => {
  return (
    <div className="pa2">
      <input
        className="pa3 ba b--green bg-lightest-blue"
        type="search"
        placeholder="model keresés..."
        onChange={searchChange}
      />
    </div>
  );
};

Ha nincs hiba, akkor a böngészőben egy frissítés után a keresőbe gépelve a konzolba kiírásra kerül az event objektum.

Az event objektumnak van egy target tulajdonsága, ami azt az elemet jelenti, amihez az esemény tartozik. Még lejjebb ásva a target-nek pedig van egy value tulajdonsága, ami az értéket fogja tartalmazni:

onSearchChange(event) {
      console.log(event.target.value);
}

Amit most már a konzolban látunk, az a keresőbe beírt érték:

kereso-keszites-state-reactjs-gyorstalpalo-5-kep6

Persze ezen kívül ez még nekünk semmit sem csinál azon kívül, hogy most már ki tudjuk logolni, hogy mi az amit valaki begépelt a keresőbe. Mondjuk ez is valami :).

Keresési logika leprogramozása

Most jön a “kemény” logika: 

  • A keresőben model nevekre fogunk szűrni, ami a models.js-ben egy objektumon belül a name tulajdonságnak felel meg.
  • Azt is tudjuk, hogy a state-en belül a models-nek kell tartalmaznia a kereső által kiválasztott terminátor modellek listáját. Tehát ez egy szűrt tömb lesz.
  • Ahhoz, hogy a keresés tökéletes legyen a keresőbe beírt szöveget mondjuk csupa kisbetűre kell alakítani a toLowerCase() beépített JavaScript függvénnyel. De persze, ha valaki nagybetűsre szeretné alakítani, azt is megteheti. És persze ugyanezt a kisbetűre alakítást meg kell csinálni majd a másik oldalon a name-el is.
  • Kézenfekvő lesz, hogy a tömböknek a filter() függvényét használjuk majd a szűréshez.
  • Az egyezéshez pedig a szintén hasznos includes() tömbfüggvényt használjuk.

Ezek alapján így néz ki a keresési logika a kódban:

onSearchChange = (event) => {
    const filteredModels = this.state.models.filter((model) => {
      return model.name
        .toLowerCase()
        .includes(this.state.searchfield.toLowerCase());
    });
    console.log(filteredModels);
  }

Ha a böngészőben megnézzük a kereső működését és a konzolt:

kereso-keszites-state-reactjs-gyorstalpalo-5-kep7

Akkor azt látjuk, hogy valami még nem klappol, mert a szűrt tömbben mindig 11 elem van, hiába szűrünk. A kutya a state-ben van elásva, mert a state-ben a kereső mező értéke mindig üres:

this.state = {
      searchfield: ""

A megoldás egyszerű. A kereső mező tartalmát vissza kell juttatni a state-be. És itt jön a második fontos szabály. A React nem engedi a state-et közvetlenül módosítani. Nem működik tehát a

this.state.searchfield = “kiskutya”;

Erre a Terminál is figyelmeztet a VSCode-ban: Do not mutate state directly. Use setState()

Azt mondja nekem, hogy ne bántsam a state-et közvetlenül. Használjam helyette a setState-et. Fontos tehát, hogy a state-et csakis a setState() függvénnyel lehet beállítani.

Tehát:

this.setState({ searchfield: event.target.value });

Most már a böngésző konzolban is jobban fest a dolog:

kereso-keszites-state-reactjs-gyorstalpalo-5-kep8

Keresési eredmények megjelenítése

Még egy dolog van hátra. Csak a szűrt Terminátorokat kell megjeleníteni. Most közvetlenül a state-ből vesszük:

<TerminatorList models={this.state.models} />

Ahhoz, hogy ide be tudjuk írni a szűrt listát (filteredModels), a logikát a render() metódusba kell áthelyezni:

onSearchChange = (event) => {
    this.setState({ searchfield: event.target.value });
  };
 
  render() {
    const filteredModels = this.state.models.filter((model) => {
      return model.name
        .toLowerCase()
        .includes(this.state.searchfield.toLowerCase());
    });
    return (
      <div className="tc">
        <h1>Terminator modellek</h1>
        <SearchBox searchChange={this.onSearchChange} />
        <TerminatorList models={filteredModels} />
      </div>
    );
  }

Github

Ahol most tartunk az alábbi Github linkről is letölthető.

YouTube

Aki pedig szeretné videón végignézni / hallgatni, amit eddig csináltunk, annak íme: