Poznajemy custom hook useObjectState znaleziony na GitHubie i omawiamy sobie, jak on działa. Do dzieła.

Ok, przykład użycia z readme:

import React from 'react';
import useObjectState from 'react-hooks-object-state';

const Example = () => {
  const [myObject, setMyObject] = useObjectState({ bool: true, string: 'foo' });

  const updateObject = () => setMyObject({ bool: false });

  return <button onClick={updateObject}>Update object</button>;
}

Przykład użycia z funkcją:

const updateObject = () => {
  setMyObject((state) => {
    return {
      string: state.str + 'bar'
    }
  })
}

Swoją drogą od takich rzeczy są reducery i useReducer, ale mniejsza, ćwiczenie to ćwiczenie. Skoro już wiemy, co budujemy, możemy przejść do rzeczy.

Funkcja pomocniczna isFilled:

function isFilledObject(obj) {
  if (
    !obj ||
    typeof obj !== 'object' ||
    Object.keys(obj).length === 0
  ) {
    return false;
  }

  return true;
}

export default useObjectState;
export {
  isFilledObject
};

Proste. Zaczynamy pisać hook, jak obiekt nie jest filled to console.warn i wyskakujemy zwracając pustą tablicę:

'use strict';

import { useState } from 'react';

function useObjectState(initObject) {
  if (!isFilledObject(initObject)) {
    console.warn('A non-empty object must be provided to useObjectState. State not created.');
    return [];
  }

  const [state, setState] = useState(initObject);

Ok, mamy state i setState, to drugie nie będzie zwrócone, będzie updateState, które sobie napiszemy:

const updateObject = (partialState) => {
    if (typeof partialState === 'function') {
      partialState = partialState(state);
    }

    if (!isFilledObject(partialState)) {
      console.warn('An object or function returning an object must be passed to useObjectState setter. State not updated.');
      return;
    }

Jak przekazał funkcję – wywołaj funkcję na stanie i zapisz do zmiennej, sprawdź, czy stan dalej jest obiektem. Dzięki temu bezpiecznie to sobie wywołaliśmy, nie mutując nic w razie, gdyby funkcja zwróciła coś, co obiektem nie jest.

I jak obiektem nie jest to wychodzimy. Jak jest to idziemy dalej. Zobaczmy to sobie jeszcze raz, bo podstępnie to jest ułożone:

const updateObject = (partialState) => {
    if (typeof partialState === 'function') {
      partialState = partialState(state);
    }

    if (!isFilledObject(partialState)) {
      console.warn('An object or function returning an object must be passed to useObjectState setter. State not updated.');
      return;
    }

Czyli tak, albo dostajemy partialState, który JEST obiektem, albo jest FUNKCJĄ, która wywołana na state da obiekt. Bezpiecznie ją sobie sprawdzamy i jeśli coś nie działa, to wychodzimy.

I to działa także na inny edge case, czyli coś zostało przekazane (niekoniecznie funkcja), co nie jest obiektem. Wtedy też wychodzimy.

Ok, wszystko gra, idziemy dalej:

let stateCopy = Object.assign({}, state);
const stateKeys = Object.keys(stateCopy);

    for (const [partialKey, partialVal] of Object.entries(partialState)) {
      if (stateKeys.includes(partialKey)) {
        if (typeof stateCopy[partialKey] === 'object') {
          Object.assign(stateCopy[partialKey], partialVal);
        } else {
          stateCopy[partialKey] = partialVal;
        }
      }
    }

    setState(stateCopy);
  };

  return [state, updateObject];
}

Wyjaśnienie:

  • Object assign kopiuje do target (pierwszy argument) rzeczy z sources
  • Object keys zwraca klucze
  • let stateCopy będzie jeszcze mutowane
  • const stateKeys będzie tylko czytane
  • Object entries zwraca pseudotuplę z parami klucz wartość
  • Iterujemy po kluczach partialState
  • Sprawdzamy, czy taki klucz jest poprawny (jest w stateKeys)
  • Sprawdzamy, czy stateCopy ma tam pod kluczem object i jak ma to robimy tam assign obiektu
  • Jak to nie obiekt, tylko wartość prosta to ją przypisujemy normalnie (dla obiektów złożonych aby utworzyć kopię nie referencję potrzebujemy assign, nie przypiosania)
  • Na końcu używamy setState
  • Zwracamy pseudotuplę state, updateObject

Mam nadzieję, że przykład rozruszał nasze szare komórki!