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:

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:

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:

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:

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!”.

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:

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:

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:

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:

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: