Lekcja 8: Renderowanie Warunkowe

W aplikacjach React często potrzebujemy wyświetlać różne elementy interfejsu w zależności od określonych warunków, takich jak stan aplikacji, dane użytkownika czy propsy. React oferuje kilka elastycznych sposobów na realizację renderowania warunkowego.

1. Instrukcja `if` wewnątrz Komponentu

Możemy używać standardowych instrukcji warunkowych JavaScript (if/else) przed zwróceniem JSX z komponentu, aby zdecydować, co ma zostać wyrenderowane.

import React from \'react\';

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>Witaj ponownie!</h1>;
  } else {
    return <h1>Proszę się zalogować.</h1>;
  }
}

function App() {
  const [loggedIn, setLoggedIn] = React.useState(false);

  return (
    <div>
      <Greeting isLoggedIn={loggedIn} />
      <button onClick={() => setLoggedIn(!loggedIn)}>
        {loggedIn ? \'Wyloguj\' : \'Zaloguj\'}
      </button>
    </div>
  );
}

export default App;

Możemy również przypisać wynik warunku do zmiennej i użyć jej w JSX.

function LoginStatus({ isLoggedIn }) {
  let statusMessage;
  if (isLoggedIn) {
    statusMessage = <p>Jesteś zalogowany.</p>;
  } else {
    statusMessage = <p>Nie jesteś zalogowany.</p>;
  }

  return <div>{statusMessage}</div>;
}

2. Operator Trójargumentowy (Warunkowy) `? :`

Operator trójargumentowy (condition ? valueIfTrue : valueIfFalse) jest bardzo przydatny do osadzania prostych warunków bezpośrednio w JSX. Jest to często bardziej zwięzłe niż użycie if/else poza JSX.

import React, { useState } from \'react\';

function Mailbox({ unreadMessages }) {
  const count = unreadMessages.length;
  return (
    <div>
      <h1>Skrzynka odbiorcza</h1>
      {count > 0 ? (
        <h2>
          Masz {count} nieprzeczytanych {count === 1 ? \'wiadomość\' : \'wiadomości\'}.
        </h2>
      ) : (
        <p>Brak nowych wiadomości.</p>
      )}
    </div>
  );
}

function App() {
  const [messages, setMessages] = useState([\'Re: Spotkanie\', \'Pilne!\']);

  return <Mailbox unreadMessages={messages} />;
}

export default App;

3. Operator Logiczny AND `&&`

Operator && jest użyteczny, gdy chcemy wyrenderować coś tylko wtedy, gdy warunek jest prawdziwy, a w przeciwnym razie nie renderować niczego. Działa to, ponieważ w JavaScript true && expression zawsze zwraca expression, a false && expression zawsze zwraca false. React nie renderuje wartości false, null, undefined ani true.

function WarningBanner({ showWarning }) {
  return (
    <div>
      {showWarning && 
        <div className="warning">
          Uwaga! To jest ostrzeżenie.
        </div>
      }
      <p>Główna treść strony...</p>
    </div>
  );
}

function App() {
  const [displayWarning, setDisplayWarning] = useState(true);

  return (
    <div>
      <WarningBanner showWarning={displayWarning} />
      <button onClick={() => setDisplayWarning(!displayWarning)}>
        {displayWarning ? \'Ukryj\' : \'Pokaż\'} ostrzeżenie
      </button>
    </div>
  );
}

Uwaga: Uważaj na wartości "fałszywe" (falsy) inne niż false, np. 0. Wyrażenie count && <Element />, gdzie count wynosi 0, spowoduje wyrenderowanie 0 w DOM, co zazwyczaj nie jest pożądane. W takich przypadkach lepiej użyć operatora trójargumentowego: count > 0 ? <Element /> : null.

4. Zapobieganie Renderowaniu Komponentu (zwracanie `null`)

Jeśli chcesz całkowicie zapobiec renderowaniu komponentu pod pewnym warunkiem, możesz sprawić, by zwracał on null.

function AdminPanel({ isAdmin }) {
  if (!isAdmin) {
    return null; // Nie renderuj niczego, jeśli użytkownik nie jest adminem
  }

  return (
    <div className="admin-panel">
      <h2>Panel Administratora</h2>
      {/* ... zawartość panelu */}
    </div>
  );
}

function App() {
  const [userRole, setUserRole] = useState(\'user\'); // \'user\' lub \'admin\'

  return (
    <div>
      <p>Witaj, użytkowniku!</p>
      <AdminPanel isAdmin={userRole === \'admin\'} />
      {/* Przycisk do zmiany roli dla demonstracji */}
      <button onClick={() => setUserRole(userRole === \'admin\' ? \'user\' : \'admin\')}>
        Zmień rolę
      </button>
    </div>
  );
}

Zwrócenie null z metody render (w komponentach klasowych) lub z komponentu funkcyjnego nie wpływa na uruchamianie metod cyklu życia komponentu (lub efektów w useEffect). Po prostu nic nie jest renderowane do DOM.

Wybór Odpowiedniej Metody


Ćwiczenie praktyczne

Pokaż rozwiązanie
// src/LoadingStatus.jsx
import React, { useState } from \'react\';

function LoadingStatus() {
  const [status, setStatus] = useState(\'idle\'); // \'idle\', \'loading\', \'success\', \'error\'

  let content;
  if (status === \'loading\') {
    content = <p>Ładowanie danych... Proszę czekać.</p>;
  } else if (status === \'success\') {
    content = <p style={{ color: \'green\' }}>Dane zostały pomyślnie załadowane!</p>;
  } else if (status === \'error\') {
    content = <p style={{ color: \'red\' }}>Wystąpił błąd podczas ładowania danych.</p>;
  } else { // idle
    content = <p>Kliknij przycisk, aby rozpocząć ładowanie.</p>;
  }

  return (
    <div>
      <h2>Status Ładowania</h2>
      {content}
      <div>
        <button onClick={() => setStatus(\'loading\')} disabled={status === \'loading\'}>
          Rozpocznij ładowanie
        </button>
        <button onClick={() => setStatus(\'success\')} disabled={status !== \'loading\'}>
          Symuluj Sukces
        </button>
        <button onClick={() => setStatus(\'error\')} disabled={status !== \'loading\'}>
          Symuluj Błąd
        </button>
        <button onClick={() => setStatus(\'idle\')} disabled={status === \'idle\'}>
          Resetuj
        </button>
      </div>
    </div>
  );
}

export default LoadingStatus;

// W App.jsx
import React from \'react\';
import LoadingStatus from \'./LoadingStatus\";

function App() {
  return (
    <div>
      <LoadingStatus />
    </div>
  );
}

export default App;

Cel: Stworzyć komponent, który wyświetla różne komunikaty w zależności od statusu ładowania danych (np. "Ładowanie...", "Dane załadowane!", "Błąd ładowania.").

Kroki:

  1. Stwórz komponent funkcyjny LoadingStatus.
  2. Użyj useState do przechowywania statusu ładowania, np. status, który może przyjmować wartości: \'idle\', \'loading\', \'success\', \'error\'. Zainicjuj go na \'idle\'.
  3. Dodaj przyciski symulujące rozpoczęcie ładowania, sukces i błąd, które będą zmieniać stan status.
  4. W komponencie LoadingStatus użyj renderowania warunkowego (np. if/else if/else lub zagnieżdżonych operatorów trójargumentowych), aby wyświetlić odpowiedni komunikat w zależności od wartości stanu status.
  5. Użyj komponentu LoadingStatus w App.jsx.

Zadanie do samodzielnego wykonania

Stwórz komponent UserProfileCard, który przyjmuje props user (obiekt z polami name, email, isAdmin - boolean). Komponent powinien:

  1. Wyświetlać imię użytkownika (user.name).
  2. Wyświetlać email użytkownika (user.email).
  3. Jeśli user.isAdmin jest true, wyświetlić dodatkowo odznakę "(Admin)" obok imienia (użyj operatora &&).
  4. Jeśli obiekt user nie zostanie przekazany (props user jest null lub undefined), komponent powinien zwrócić null (nie renderować niczego).
  5. Przetestuj komponent w App.jsx, przekazując różne obiekty user (w tym null).

FAQ - Renderowanie Warunkowe

Czy mogę używać `switch` do renderowania warunkowego?

Tak, instrukcja `switch` może być używana poza JSX do decydowania, który element lub komponent zwrócić, podobnie jak `if/else`. Nie można jej jednak używać bezpośrednio wewnątrz nawiasów klamrowych `{}` w JSX, ponieważ `switch` jest instrukcją, a nie wyrażeniem.

Która metoda renderowania warunkowego jest najlepsza?

Nie ma jednej "najlepszej" metody. Wybór zależy od złożoności warunku i preferencji czytelności. Proste warunki inline dobrze obsługują operatory `? :` i `&&`. Bardziej złożona logika lub zwracanie zupełnie innych struktur często lepiej wygląda z użyciem `if/else` lub `switch` przed instrukcją `return`.

Czy renderowanie warunkowe wpływa na wydajność?

Samo renderowanie warunkowe jest bardzo wydajne. React efektywnie aktualizuje DOM tylko wtedy, gdy wynik warunku się zmienia. Problemy z wydajnością mogą pojawić się, jeśli warunki są bardzo skomplikowane lub jeśli zmiana warunku powoduje renderowanie bardzo dużych i złożonych drzew komponentów.

Jak warunkowo dodawać atrybuty do elementu JSX?

Można użyć operatora trójargumentowego lub `&&` do warunkowego ustawiania wartości atrybutu. Jeśli warunek jest fałszywy, można przypisać `null` lub `undefined`, co spowoduje, że React pominie ten atrybut. Dla atrybutów typu boolean (np. `disabled`), można przekazać `true` lub `false`.

Czy mogę renderować różne komponenty w zależności od warunku?

Tak, jest to bardzo częsty wzorzec. Możesz użyć dowolnej metody renderowania warunkowego, aby zdecydować, który komponent (np. `` czy ``) ma zostać wyrenderowany w danym miejscu.

Co się stanie, gdy użyję `0 &&`?

Ponieważ `0` jest wartością "falsy" w JavaScript, całe wyrażenie `0 && ` zwróci `0`. React wyrenderuje `0` jako tekst w DOM, co zazwyczaj nie jest pożądane. Aby uniknąć renderowania czegokolwiek w takim przypadku, użyj jawnego porównania: `count > 0 && ` lub operatora trójargumentowego: `count ? : null`.

Czy zwrócenie `null` z komponentu jest tym samym co nie wywołanie funkcji renderującej?

Nie do końca. Komponent nadal jest montowany i przechodzi przez swój cykl życia (Hooki `useState` i `useEffect` nadal działają). Zwrócenie `null` jedynie informuje React, aby nie renderował żadnego wyjścia DOM dla tego komponentu w danym cyklu renderowania.