Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!

🔥 Webinar Q&A na temat IT – pytaj, o co chcesz! już 24.11.2022 (czwartek) o 20:30

Dynamiczne renderowanie komponentów w React

Twórz optymalne rozwiązania

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ć.

Spis treści

 Pierwszy raz? Zapoznaj się z JavaScriptem

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.

 Klasyczne renderowanie komponentów

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.

 Komponenty kwestionariusza

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ą:

Trzy strony kwestionariusza wyrenderowane jednocześnie

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.

 Nieoptymalne warunkowe renderowanie komponentów

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.

 Dynamiczne renderowanie komponentów

 Tablica z komponentami

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

 Komponent renderowany dynamicznie

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ć.

 Automatyczne generowanie numerów stron

 Przeniesienie kodu do nowego komponentu

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ę.

 Generowanie dowolnej liczby przycisków

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.

 Co można ulepszyć

Jeżeli masz ochotę popracować z kwestionariuszem, to możesz jeszcze zadbać o kilka aspektów, na przykład:

  • z elementu button zrobić osobny komponent i importować go do <Pagination />
  • stworzyć rozwiązanie, dzięki któremu numer aktywnej strony kwestionariusza będzie w jakiś sposób wyróżniony (np. podkreślony)
  • zapisywać w stanie aplikacji informację z textarea z komponentu <Question />, tak aby nie kasowała się, gdy użytkownik przejdzie na inną stronę,
  • sprawdzać, czy posiadamy prawidłowe dane dotyczące zakresu stron – czy stan nie zawiera numeru strony, który poskutkuje nieistniejącym indeksem przy wyborze komponentu z tablicy.

 

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ł:

Mentoring to efektywna nauka pod okiem doświadczonej osoby, która:

  • przekazuje Ci swoją wiedzę i nadzoruje Twoje postępy w zdobywaniu umiejętności,
  • uczy Cię dobrych praktyk i wyłapuje złe nawyki,
  • wspiera Twój rozwój i zwiększa zaangażowanie w naukę.