Kontynuacja poznawania hooka useState, tym razem patrzymy na initializer function oraz stan jako obiekt. Do dzieła.

Ok, oto przykład initializer function:

import { useState } from 'react';

function createInitialTodos() {
  const initialTodos = [];
  for (let i = 0; i < 50; i++) {
    initialTodos.push({
      id: i,
      text: 'Item ' + (i + 1)
    });
  }
  return initialTodos;
}

export default function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos);
  const [text, setText] = useState('');

  return (
    <>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={() => {
        setText('');
        setTodos([{
          id: todos.length,
          text: text
        }, ...todos]);
      }}>Add</button>
      <ul>
        {todos.map(item => (
          <li key={item.id}>
            {item.text}
          </li>
        ))}
      </ul>
    </>
  );
}

Ta funkcja znajduje się powyżej poziomu RFC, więc nie jest definiowana od zera przy każdym re-renderze. Jest również w odpowiedni sposób przekazana, tak, aby była używana tylko za pierwszym renderem.

A jak wyglądałby nieodpowiedni sposób? Tak:

export default function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos());
  const [text, setText] = useState('');

Co do samego trzymania funkcji powyżej RFC – nie musimy. Możemy wewnątrz RFC, ale wtedy zróbmy im useCallback, jeżeli definiowanie od zera będzie dla nas uciążliwe (nie chodzi tu tylko o optymalizację, definiowanie od zera tej samej funkcji i przekazywanie jej jako prop to de facto przekazywanie innej i wymuszanie re-rendera na dziecku, choć teoretycznie nic się nie zmienia).

Ok, teraz initializer function w praktyce – custom hook useLocalStorage:

import { useState, useEffect } from "react";

export function useLocalStorageState(initialState, key) {
  const [value, setValue] = useState(function () {
    const storedValue = localStorage.getItem(key);
    return storedValue ? JSON.parse(storedValue) : initialState;
  });

  useEffect(
    function () {
      localStorage.setItem(key, JSON.stringify(value));
    },
    [value, key]
  );

  return [value, setValue];
}

Teraz tak:

  • JSON stringify pozwala zamienić obiekt/tablicę na string w formacie JSON (a zatem możemy trzymać w localstorage obiekty złożone)
  • JSON parse parsuje odczytywany JSON string na obiekt JS
  • Funkcja initializer sprawdza, czy obiekt o takim kluczu nie jest już obecny w localstorage – dzięki temu jeżeli coś tam jest zostawione po poprzednim mountowaniu komponentu to używamy tego
  • Jeżeli nie, to funkcja initializer zwraca initialstate…
  • useEffect gdy tylko zmieni się value albo key, bo takie są zależności, zapisuje aktualną wartość stanu do localstorage
  • W JS nie ma multiple returns jak w Pythonie (tupla), ale znaną nam konwencją zwracamy tablicę z kilkoma elementami

Stan jako obiekt lub tablicę poznamy w następnych lekcjach.