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!