Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!
⛔ Potrzebujesz wsparcia? Oceny CV? A może Code Review? ✅ Dołącz do Discorda!
Jeśli Reacta znasz od niedawna, możesz jeszcze nie wiedzieć, w jaki sposób usprawniać swoją pracę i kod. Z tego artykułu dowiesz się, jak unikać tworzenia długich bloków else if przy warunkowym renderowaniu komponentów oraz jak porządkować kod, by w przyszłości łatwiej go było rozwijać.
Jeżeli poniższe rozwiązanie sprawia Ci trudność, ale mimo to chcesz dowiedzieć się, jak można unikać długich instrukcji warunkowych w JavaScripcie, to zapraszam Cię do innego mojego artykułu: Mniej instrukcji warunkowych.
Jeśli komponent reactowy trzymamy w osobnym pliku, wystarczy go zaimportować i wyrenderować w komponencie-rodzicu, który wyświetlamy użytkownikowi. W naszym przypadku tym komponentem-rodzicem jest <App />. Wyświetlamy w nim kolejne strony kwestionariusza: powitanie, pytanie i podziękowanie.
Jeśli chcesz sprawdzać kod razem ze mną, pobierz go z repozytorium na GitHubie (to gotowe rozwiązanie, ale nic nie stoi na przeszkodzie, abyś wyczyścił plik App.js i przekleił kod poniżej). Działanie rozwiązania możesz też podejrzeć na żywo.
Najpierw wyrenderujmy wszystkie strony kwestionariusza naraz, aby zobaczyć, z czym mamy do czynienia:
import React from 'react'; import Question from './Question'; import WelcomePage from './WelcomePage'; import ThanksPage from './ThanksPage'; class App extends React.Component { render() { return ( <main className="questionnaire"> <WelcomePage /> <Question /> <ThanksPage /> </main> ); } } export default App;
Wszystkie trzy strony pokazują się jedna pod drugą:
Jak widzisz, nie ma tam nic skomplikowanego. Każdy komponent to osobna karta z innym tytułem i elementem (opisem lub polem tekstowym textarea).
Uwaga: komponent <App /> jest klasowy, a reszta funkcyjna. Równie dobrze mogłoby być na odwrót. Chcę Ci pokazać, że rodzaj komponentu nie ma znaczenia dla działania tego rozwiązania.
Nasza paginacja to w tej chwili trzy przyciski z numerami 1 (dla WelcomePage), 2 (Question) i 3 (ThanksPage), a aktualny numer strony przechowujemy w stanie komponentu. Kliknięcia obsługuje funkcja handleClick().
class App extends React.Component { state = { page: '1', // chcemy, by jako pierwsza wyświetlała się strona WelcomePage }; handleClick = e => { this.setState({ ...this.state, page: e.target.value }); // do stanu komponentu zapisujemy informację o numerze strony }; render() { return ( <main className="questionnaire"> {/* tu będziemy renderować karty kwestionariusza */} <div className="questionnaire__pagination pagination"> <button className="pagination__number" value="1" onClick={this.handleClick} > 1 </button> <button className="pagination__number" value="2" onClick={this.handleClick} > 2 </button> <button className="pagination__number" value="3" onClick={this.handleClick} > 3 </button> </div> </main> ); } }
Chcemy teraz wyświetlać kolejne karty kwestionariusza osobno, zależnie od numeru strony. Rozwiązaniem, z którego moglibyśmy skorzystać przy tworzeniu pierwszej wersji kodu lub np. proof of concept, jest zastosowanie warunków ( if lub switch statement). Ja zrobiłem to w taki sposób:
class App extends React.Component { state = { page: '1', // chcemy, by jako pierwsza wyświetlała się strona WelcomePage }; handleClick = e => { this.setState({ ...this.state, page: e.target.value }); // do stanu komponentu zapisujemy informację o numerze strony }; renderCard() { if (this.state.page === '1') { return <WelcomePage />; } else if (this.state.page === '2') { return <Question />; } else if (this.state.page === '3') { return <ThanksPage />; } } render() { return ( <main className="questionnaire"> {this.renderCard()} <div className="questionnaire__pagination pagination"> {/* kod paginacji */} </div> </main> ); } }
A teraz pytanie: co by się stało, gdybyśmy chcieli stworzyć kolejną stronę kwestionariusza?
Musielibyśmy nie tylko dodać kolejny warunek else if, ale również nowy przycisk z numerem strony – i tak za każdym razem, gdy kwestionariusz byłby rozszerzany o nowe karty. Nie wygląda to dobrze. Nasze rozwiązanie trudno będzie rozwijać. Z pomocą przychodzi nam dynamiczne renderowanie komponentów.
Numery stron kwestionariusza to liczby całkowite. Czy coś Ci to przypomina? Mnie indeksy tablicy. Dlatego przeniesiemy komponenty kwestionariusza właśnie do tablicy poza komponenten <App />. Dzięki temu nasze rozwiązanie będzie bardziej elastyczne – być może w przyszłości chcielibyśmy taką tablicę importować z innego pliku.
const pages = [WelcomePage, Question, ThanksPage]; // tablica z listą stron w odpowiedniej kolejności
Teraz wystarczy uzależnić wybór komponentu z tablicy od numeru strony. Wykorzystamy do tego indeksy. Trzeba też pamiętać o wielkiej literze w nazwie zmiennej. Taki dynamiczny komponent wstawiamy do kodu JSX jak każdy inny komponent reactowy.
const pages = [WelcomePage, Question, ThanksPage]; // tablica z listą stron w odpowiedniej kolejności class App extends React.Component { state = { page: "1", // chcemy, by jako pierwsza wyświetlała się strona WelcomePage }; handleClick = e => { this.setState({ ...this.state, page: e.target.value }); // do stanu komponentu zapisujemy informację o numerze strony }; render() { const Component = pages[this.state.page - 1]; // wykorzystujemy index, aby wybrać z tablicy komponent adekwatny do numeru strony return ( <main className="questionnaire"> <Component /> <div className="questionnaire__pagination pagination"> {/* kod paginacji */} </div> </main> ); } }
I to wszystko w kwestii dynamicznego renderowania komponentów. Została nam jednak jeszcze sprawa paginacji. W tej chwili po dodaniu nowego komponentu do tablicy i tak musimy w paginacji ręcznie dodać kolejny element button. Nadal więc nasze rozwiązanie będzie sprawiać trudności przy rozszerzaniu kwestionariusza. Czas to naprawić.
Najpierw przeniosę nasz div z paginacją do osobnego komponentu <Pagination /> i umieszczę go w pliku o tej samej nazwie.
import React from 'react'; const Pagination = props => { const { handleClick } = props; return ( <div className="questionnaire__pagination pagination"> <button className="pagination__number" value="1" onClick={handleClick} > 1 </button> <button className="pagination__number" value="2" onClick={handleClick} > 2 </button> <button className="pagination__number" value="3" onClick={handleClick} > 3 </button> </div> ); }; export default Pagination;
Od razu też zaimportuję go do pliku App.js i umieszczę w miejscu przycisków. Musimy jeszcze pamiętać o przekazaniu przez props funkcji handleClick():
import React from 'react'; import Question from './Question'; import WelcomePage from './WelcomePage'; import ThanksPage from './ThanksPage'; import Pagination from './Pagination'; const pages = [WelcomePage, Question, ThanksPage]; // tablica z listą stron w odpowiedniej kolejności class App extends React.Component { state = { page: "1", // chcemy, by jako pierwsza wyświetlała się strona WelcomePage }; handleClick = e => { this.setState({ ...this.state, page: e.target.value }); // do stanu komponentu zapisujemy informację o numerze strony }; render() { const Component = pages[this.state.page - 1]; // wykorzystujemy index, aby wybrać z tablicy komponent adekwatny do numeru strony return ( <main className="questionnaire"> <Component /> <Pagination handleClick={this.handleClick} /> </main> ); } }
Jak widzisz powyżej, funkcję handleClick() przekazuję przez props, ponieważ wpływa ona na stan komponentu <App /> – w związku z tym tam muszę mieć jej definicję.
Nasze przyciski wyglądają niemal identycznie, różnią się tylko numerami. Oznacza to, że możemy np. dzięki pętli wygenerować dowolną liczbę buttonów… zależną od liczby kart kwestionariusza! Liczbę kart do komponentu <Pagination /> przekażemy przez props:
<Pagination handleClick={this.handleClick} numberOfPages={pages.length}/>
Teraz mamy już wszystko, by wygenerować przyciski. Najpierw tworzymy pustą tablicę pageNumbers – to tam będziemy pushować wytworzone w pętli buttony. Tak natomiast wygląda funkcja createPageNumbers():
const Pagination = props => { const { handleClick, numberOfPages } = props; const pageNumbers = []; function createPageNumbers(num) { for (let i = 1; i <= num; i++) { pageNumbers.push( <button className="pagination__number" key={i} value={i} onClick={handleClick} > {i} </button> ); } return pageNumbers } return ( <div className="questionnaire__pagination pagination"> {/* tu wyrenderujemy numery stron */} </div> ); };
Zwróć uwagę, że jedyną dodatkową rzeczą w buttonie jest klucz (key).
Naszą funkcję wywołujemy bezpośrednio w kodzie JSX, korzystając z tego, że React tablicę elementów wyświetla od razu, bez stosowania np. rozproszenia.
const Pagination = props => { const { handleClick, numberOfPages } = props; const pageNumbers = []; function createPageNumbers(num) { for (let i = 1; i <= num; i++) { pageNumbers.push( <button className="pagination__number" key={i} value={i} onClick={handleClick} > {i} </button> ); } return pageNumbers } return ( <div className="questionnaire__pagination pagination"> {createPageNumbers(numberOfPages)} </div> ); };
I to już wszystko. Nasze rozwiązanie jest teraz bardziej elastyczne i łatwiejsze do rozwijania. Cały kod znajdziesz w repozytorium na GitHubie, a jego działanie podejrzysz dzięki GitHub Pages.
Jeżeli masz ochotę popracować z kwestionariuszem, to możesz jeszcze zadbać o kilka aspektów, na przykład:
Dynamiczne renderowanie komponentów w pracy z Reactem jest wykorzystywane na co dzień. Tablica to nie jedyne możliwe rozwiązanie. Przykładowo, gdyby różni klienci wymagali nieco innych kart kwestionariusza, informacje o tym moglibyśmy przechowywać w postaci obiektów, a je z kolei wedle potrzeb np. wysyłać na serwer w formacie JSON (który, jak zapewne wiesz, zbliżony jest do obiektu).
Udostępnij ten artykuł:
Potrzebujesz cotygodniowej dawki motywacji?
Zapisz się i zgarnij za darmo e-book o wartości 39 zł!
PS. Zazwyczaj rozsyłam 1-2 wiadomości na tydzień. Nikomu nie będę udostępniał Twojego adresu email.
Chcesz zostać (lepszym) programistą i lepiej zarabiać?
🚀 Porozmawiajmy o nauce programowania, poszukiwaniu pracy, o rozwoju kariery lub przyszłości branży IT!
Umów się na ✅ bezpłatną i niezobowiązującą rozmowę ze mną.
Chętnie porozmawiam o Twojej przyszłości i pomogę Ci osiągnąć Twoje cele! 🎯