Poznajemy dalej hook useEffect, plus uczymy się pisać własne hooki Reacta w oparciu o useEffect. Do dzieła.

Ok, Chatroom. Najpierw App.js, posiadające stan przekazywany niżej jako props:


export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}

Warto zwrócić uwagę, że propsy w sumie to nie są mutowalne – zmiana stanu powoduje re-render komponentu z nową wartością propsa przekazywanego w jedynym słusznym kierunku, czyli w dół.

Warto też sobie odnotować, że zmiana stanu rodzica, np. apki głównej to także re-render jego dzieci.

Ok, teraz to dziecko odbierające roomId czyli ChatRoom:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

Czyli mamy roomId jako zależność z propsów oraz serverUrl, jako stan ChatRooma. To są zależności useEffect, jeżeli one się zmienią, to odpalany jest efekt. I odwrotnie – na chwilę przed re-renderem odpalany jest cleanup.

Sam chat.js, jeżeli do czegokolwiek nam potrzebny to jest tylko fejkowa rzecz do przetestowania useEffect:

export function createConnection(serverUrl, roomId) {
  // A real implementation would actually connect to the server
  return {
    connect() {
      console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
    },
    disconnect() {
      console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl);
    }
  };
}

Ok, a teraz prawdziwa bomba – piszemy własny hook useChatRoom:

import { useEffect } from 'react';
import { createConnection } from './chat.js';

export function useChatRoom({ serverUrl, roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl]);
}

Teraz nasz chatroom może wyglądać tak:

import { useState } from 'react';
import { useChatRoom } from './useChatRoom.js';

function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });

  return (
    <>
      <label>
        Server URL:{' '}
        <input
          value={serverUrl}
          onChange={e => setServerUrl(e.target.value)}
        />
      </label>
      <h1>Welcome to the {roomId} room!</h1>
    </>
  );
}

Działanie to samo, logika ta sama, ale bardziej modularny (i reużywalny) kod, prawda, że piękniej?