Renderowanie warunkowe

React umożliwia tworzenie odrębnych komponentów, które hermetyzują (ang. encapsulate) pożądane przez ciebie metody. Wybrane komponenty mogą być renderowane bądź nie, w zależności od stanu twojej aplikacji.

Renderowanie warunkowe działa w Reakcie tak samo, jak instrukcje warunkowe w javascripcie. Aby stworzyć elementy odzwierciedlające aktualny stan aplikacji, należy użyć instrukcji if lub operatora warunkowego oraz pozwolić Reactowi je dopasować poprzez aktualizację interfejsu użytkownika.

Rozważmy następujące dwa komponenty:

function UserGreeting(props) {
  return <h1>Witamy ponownie!</h1>;
}

function GuestGreeting(props) {
  return <h1>Proszę się zarejestrować.</h1>;
}

Stworzymy komponent Greeting (pol. Powitanie), który wyświetlał będzie pierwszy lub drugi z powyższych komponentów w zależności od tego czy użytkownik jest zalogowany.

function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
  // Spróbuj zmienić na isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

Przetestuj kod na CodePen

Powitanie renderowane przez kod w powyższym przykładzie zależy od wartości właściwości isLoggedIn.

Zmienne elementowe

Elementy mogą być przechowywane w zmiennych. Pozwala to na warunkowe renderowanie określonej części komponentu, podczas gdy pozostałe dane wyjściowe nie ulegają zmianie.

Przyjrzyjmy się dwóm nowym komponentom tworzącym przyciski logowania “Zaloguj się” (ang. Login) oraz “Wyloguj się” (ang. Logout):

function LoginButton(props) {
  return (
    <button onClick={props.onClick}>
      Zaloguj się
    </button>
  );
}

function LogoutButton(props) {
  return (
    <button onClick={props.onClick}>
      Wyloguj się
    </button>
  );
}

W przykładzie poniżej zbudujemy komponent ze stanem o nazwie LoginControl (pol. kontrola logowania)

W zależności od aktualnego stanu, będzie on renderował przycisk logowania (<LoginButton />) lub wylogowania <LogoutButton /> . Będzie on również renderował komponent <Greeting /> z poprzedniego przykładu:

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;

    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

Przetestuj kod na CodePen

Deklarowanie zmiennej oraz stosowanie instrukcji if to dobry sposób na warunkowe renderowanie komponentu. Czasem jednak przydaje się nieco krótsza składnia. JSX umożliwia kilka różnych opcji warunków wewnątrzliniowych. Przedstawiamy je poniżej.

Wewnątrzliniowy warunek if z użyciem logicznego operatora &&

JSX umożliwia stosowanie w nawiasach klamrowych wszelkich wyrażeń, łącznie z javascriptowym operatorem logicznym &&. Jest to przydatne, gdy chcemy jakiś element dołączyć warunkowo.

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Cześć!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          Masz {unreadMessages.length} nieprzeczytanych wiadomości.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

Przetestuj kod na CodePen

Powyższy kod działa, ponieważ w JavaScripcie true && *wyrażenie* zawsze jest ewaluowane jako wyrażenie, natomiast false && wyrażenie jako false.

Zatem jeśli warunek zwraca true, element następujący bezpośrednio po operatorze && zostanie uwzględniony w danych wyjściowych. Natomiast jeśli warunek zwróci false, React zignoruje go i pominie przy renderowaniu.

Skrócona forma if-else z operatorem warunkowym

Kolejną metodą renderowania warunkowego wewnątrz wyrażenia jest stosowanie javascriptowego operatora warunkowego warunek ? true : false.

W przykładzie poniżej używamy go, aby warunkowo wyrenderować niewielki blok tekstu.

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      Użytkownik jest teraz <b>{isLoggedIn ? 'zalogowany' : 'niezalogowany'}</b>.
    </div>
  );
}

Rozwiązanie to może być stosowane również w przypadku dłuższych wyrażeń:

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn ? (
        <LogoutButton onClick={this.handleLogoutClick} />
      ) : (
        <LoginButton onClick={this.handleLoginClick} />
      )}
    </div>
  );
}

Czytelność takich wyrażeń jest oczywiście nieco mniejsza. Podobnie jak w JavaScripcie, wybór odpowiedniego stylu zależy od preferencji twoich i twojego zespołu. Jednocześnie należy pamiętać, że kiedy warunki stają się nazbyt złożone, dobrze jest rozważyć możliwość wydzielenia osobnego komponentu

Zapobieganie renderowaniu komponentu

W sporadycznych przypadkach może zachodzić potrzeba ukrycia się komponentu, mimo iż został on już wyrenderowany przez inny komponent. Aby to umożliwić, należy zwrócić zamiast niego wartość null.

W przykładzie poniżej, renderowanie baneru ostrzegawczego <WarningBanner /> jest uzależnione od wartości właściwości o nazwie warn (pol. ostrzeż). Jeśli jest ona równa false, wówczas komponent ten nie jest renderowany.

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Ostrzeżenie!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true};
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(state => ({
      showWarning: !state.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Ukryj' : 'Pokaż'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('root')
);

Przetestuj kod na CodePen

Zwrócenie wartości null w metodzie render danego komponentu nie ma wpływu na wywoływanie metod cyklu życia tego komponentu. To znaczy, że np. metoda componentDidUpdate w dalszym ciągu zostanie wywołana.