Odabir strukture state-a
Pravilno strukturiranje state-a može napraviti razliku između komponente koju je lako menjati i debug-ovati, i one koja je stalan izvor bug-ova. Ovde se nalazi par saveta koje trebate razmotriti kada strukturirate state.
Naučićete:
- Kada da koristite jednu ili više state promenljivih
- Šta izbegavati prilikom organizacije state-a
- Kako popraviti česte probleme sa strukturom state-a
Principi za strukturiranje state-a
Kada pišete komponentu koja sadrži neki state, moraćete da odlučite koliko state promenljivih da koristite i kakav oblik podataka u njima vam je potreban. Iako je moguće da napišete pravilan program sa neoptimizovanom strukturom state-a, postoji par principa koji vas mogu uputiti da donesete bolje odluke:
- Grupisati povezane state-ove. Ako uvek istovremeno ažurirate dve ili više state promenljivih, pokušajte da ih spojite u jednu state promenljivu.
- Izbegavati kontradikcije u state-u. Kada je state strukturiran tako da više delova state-a budu kontradiktorni i “neodgovarajući” jedni drugima, ostavljate prostora za greške. Pokušajte ovo da izbegnete.
- Izbegavati suvišan state. Ako, tokom renderovanja, neku informaciju možete izračunati na osnovu props-a komponente ili već postojeće state promenljive, ne biste trebali da stavite tu informaciju u state komponente.
- Izbegavati dupliranje u state-u. Kada su isti podaci duplirani u više state promenljivih, ili unutar ugnježdenih objekata, teško je sinhronizovati ih. Smanjite dupliranje kada to možete.
- Izbegavati duboko ugnježedeni state. Hijerarhijski dubok state nije zgodan za ažuriranje. Kada je moguće, preferirajte da strukturirate state da bude flat.
Cilj iza ovih principa je da napravite da se state lako ažurira bez uvođenja grešaka. Uklanjanje suvišnih i dupliranih podataka iz state-a pomaže da svi njegovi delovi ostanu sinhronizovani. Ovo je slično onome kako inženjeri baza podataka žele da “normalizuju” strukturu baze podataka i smanje šanse za bug-ove. Da parafraziramo Alberta Ajnštajna, “Napravite vaš state što jednostavnijim—ali ne jednostavnijim od toga.”
Hajde da vidimo primenu ovih principa na delu.
Grupisati povezane state-ove
Ponekad se možete dvoumiti između upotrebe jedne ili više state promenljivih.
Da li koristiti ovo?
const [x, setX] = useState(0);
const [y, setY] = useState(0);
Ili ovo?
const [position, setPosition] = useState({ x: 0, y: 0 });
Tehnički, možete koristiti oba pristupa. Ali, ako se dve state promenljive uvek menjaju zajedno, može biti dobra ideja da ih grupišete u jednu state promenljivu. Tako nećete zaboravati da ih držite sinhronizovane, kao u ovom primeru gde pomeranje kursora ažurira obe koordinate crvene tačke:
import { useState } from 'react'; export default function MovingDot() { const [position, setPosition] = useState({ x: 0, y: 0 }); return ( <div onPointerMove={e => { setPosition({ x: e.clientX, y: e.clientY }); }} style={{ position: 'relative', width: '100vw', height: '100vh', }}> <div style={{ position: 'absolute', backgroundColor: 'red', borderRadius: '50%', transform: `translate(${position.x}px, ${position.y}px)`, left: -10, top: -10, width: 20, height: 20, }} /> </div> ) }
Drugi slučaj gde možete grupisati podatke u objekat ili niz je kada ne znate koliko state-ova vam treba. Na primer, korisno je kada imate formu gde korisnik može uneti polja po sopstvenoj želji.
Izbegavati kontradikcije u state-u
Ovde je feedback forma za hotel sa isSending
i isSent
state promenljivama:
import { useState } from 'react'; export default function FeedbackForm() { const [text, setText] = useState(''); const [isSending, setIsSending] = useState(false); const [isSent, setIsSent] = useState(false); async function handleSubmit(e) { e.preventDefault(); setIsSending(true); await sendMessage(text); setIsSending(false); setIsSent(true); } if (isSent) { return <h1>Hvala za feedback!</h1> } return ( <form onSubmit={handleSubmit}> <p>Kako vam je bilo u The Prancing Pony hotelu?</p> <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <br /> <button disabled={isSending} type="submit" > Pošalji </button> {isSending && <p>Slanje...</p>} </form> ); } // Pretvaraj se da šalješ poruku. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }
Iako ovaj kod radi, ostavlja prostor za “nemoguća” stanja. Na primer, ako zaboravite da pozovete setIsSent
i setIsSending
zajedno, možete završiti u situaciji gde su i isSending
i isSent
postavljeni na true
istovremeno. Što je vaša komponenta kompleksnija, teže je razumeti šta se desilo.
Pošto isSending
i isSent
nikad ne bi trebali da budu true
istovremeno, bolje je zameniti ih sa jednom status
state promenljivom koja može imati jedno od tri validna stanja: 'typing'
(inicijalno), 'sending'
i 'sent'
:
import { useState } from 'react'; export default function FeedbackForm() { const [text, setText] = useState(''); const [status, setStatus] = useState('typing'); async function handleSubmit(e) { e.preventDefault(); setStatus('sending'); await sendMessage(text); setStatus('sent'); } const isSending = status === 'sending'; const isSent = status === 'sent'; if (isSent) { return <h1>Hvala za feedback!</h1> } return ( <form onSubmit={handleSubmit}> <p>Kako vam je bilo u The Prancing Pony hotelu?</p> <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <br /> <button disabled={isSending} type="submit" > Pošalji </button> {isSending && <p>Slanje...</p>} </form> ); } // Pretvaraj se da šalješ poruku. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }
Možete deklarisati konstante da poboljšate čitljivost:
const isSending = status === 'sending';
const isSent = status === 'sent';
Ali, one nisu state promenljive, pa ne morate brinuti o tome da li će biti sinhronizovane.
Izbegavati suvišan state
Ako, tokom renderovanja, neku informaciju možete izračunati na osnovu props-a komponente ili već postojeće state promenljive, ne biste trebali da stavite tu informaciju u state komponente.
Na primer, pogledajte ovu formu. Radi, ali, da li možete pronaći neki suvišan state u njoj?
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [fullName, setFullName] = useState(''); function handleFirstNameChange(e) { setFirstName(e.target.value); setFullName(e.target.value + ' ' + lastName); } function handleLastNameChange(e) { setLastName(e.target.value); setFullName(firstName + ' ' + e.target.value); } return ( <> <h2>Prijavite se</h2> <label> Ime:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Prezime:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Vaša karta će biti izdata na ime: <b>{fullName}</b> </p> </> ); }
Ova forma ima tri state promenljive: firstName
, lastName
i fullName
. Međutim, fullName
je suvišno. Uvek možete izračunati fullName
pomoću firstName
i lastName
tokom rendera, pa je uklonite iz state-a.
Evo kako to da uradite:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const fullName = firstName + ' ' + lastName; function handleFirstNameChange(e) { setFirstName(e.target.value); } function handleLastNameChange(e) { setLastName(e.target.value); } return ( <> <h2>Prijavite se</h2> <label> Ime:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Prezime:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Vaša karta će biti izdata na ime: <b>{fullName}</b> </p> </> ); }
Ovde, fullName
nije state promenljiva. Umesto toga, računa se tokom rendera:
const fullName = firstName + ' ' + lastName;
Kao rezultat, handler-i za promenu ne moraju ništa posebno da rade da bi je ažurirali. Kada pozovete setFirstName
ili setLastName
, pokrećete ponovni render, a onda će naredni fullName
biti izračunat na osnovu najnovijih podataka.
Deep Dive
Uobičajen primer suvišnog state-a je ovakav kod:
function Message({ messageColor }) {
const [color, setColor] = useState(messageColor);
Ovde, color
state promenljiva je inicijalizovana na messageColor
prop. Problem je u tome što ako roditeljska komponenta prosledi drugu vrednost za messageColor
kasnije (na primer, 'red'
umesto 'blue'
), color
state promenljiva neće biti ažurirana! State je inicijalizovan samo tokom prvog rendera.
Zato “preslikavanje” nekog prop-a u state promenljivu može dovesti do zabune. Umesto toga, koristite messageColor
prop direktno u kodu. Ako želite da mu date kraće ime, koristite konstantu:
function Message({ messageColor }) {
const color = messageColor;
Na ovaj način neće ostati nesinhronzovan sa prop-om prosleđenim iz roditeljske komponente.
”Preslikavanje” props-a u state ima smisla jedino ako želite da ignorišete sve promene tog prop-a. Po konvenciji, nazovite prop tako da počinje sa initial
ili default
da ukažete na to da će nove vrednosti biti ignorisane:
function Message({ initialColor }) {
// `color` state promenljiva drži *prvu* vrednost `initialColor`-a.
// Naredne promene `initialColor` prop-a su ignorisane.
const [color, setColor] = useState(initialColor);
Izbegavati dupliranje u state-u
Ova komponenta vam omogućava da odaberete jednu grickalicu za put od nekoliko ponuđenih:
import { useState } from 'react'; const initialItems = [ { title: 'perece', id: 0 }, { title: 'hrskave morske alge', id: 1 }, { title: 'musli pločica', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); return ( <> <h2>Koja je vaša grickalica za put?</h2> <ul> {items.map(item => ( <li key={item.id}> {item.title} {' '} <button onClick={() => { setSelectedItem(item); }}>Izaberi</button> </li> ))} </ul> <p>Izabrali ste {selectedItem.title}.</p> </> ); }
Trenutno se čuva izabrana stavka kao objekat u selectedItem
state promenljivoj. Međutim, ovo nije dobro: sadržaj selectedItem
-a je isti objekat koji se nalazi unutar stavke u items
listi. Ovo znači da je informacija o stavki duplirana na dva mesta.
Zašto je ovo problem? Hajde da dodamo izmenu stavki:
import { useState } from 'react'; const initialItems = [ { title: 'perece', id: 0 }, { title: 'hrskave morske alge', id: 1 }, { title: 'musli pločica', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); function handleItemChange(id, e) { setItems(items.map(item => { if (item.id === id) { return { ...item, title: e.target.value, }; } else { return item; } })); } return ( <> <h2>Koja je vaša grickalica za put?</h2> <ul> {items.map((item, index) => ( <li key={item.id}> <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedItem(item); }}>Izaberi</button> </li> ))} </ul> <p>Izabrali ste {selectedItem.title}.</p> </> ); }
Primetite da ako prvo kliknete “Izaberi”, a onda izmenite stavku, input se ažurira, ali labela na dnu ne prikazuje unete promene. To je zato što imate dupliran state, a zaboravili ste da ažurirate selectedItem
.
Iako možete ažurirati i selectedItem
takođe, lakše je ukloniti dupliranje. U ovom primeru, umesto selectedItem
objekta (koji kreira dupliranje sa objektima unutar items
liste), vi čuvate selectedId
u state-u, a onda selectedItem
dobijate pretraživanjem items
-a preko tog ID-a:
import { useState } from 'react'; const initialItems = [ { title: 'perece', id: 0 }, { title: 'hrskave morske alge', id: 1 }, { title: 'musli pločica', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedId, setSelectedId] = useState(0); const selectedItem = items.find(item => item.id === selectedId ); function handleItemChange(id, e) { setItems(items.map(item => { if (item.id === id) { return { ...item, title: e.target.value, }; } else { return item; } })); } return ( <> <h2>Koja je vaša grickalica za put?</h2> <ul> {items.map((item, index) => ( <li key={item.id}> <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedId(item.id); }}>Izaberi</button> </li> ))} </ul> <p>Izabrali ste {selectedItem.title}.</p> </> ); }
State koji je dupliran izgleda ovako:
items = [{ id: 0, title: 'perece'}, ...]
selectedItem = {id: 0, title: 'perece'}
Ali, nakon promene, sada izgleda ovako:
items = [{ id: 0, title: 'perece'}, ...]
selectedId = 0
Dupliranja nema, a ostaje samo obavezan state!
Sada, ako izmenite izabranu stavku, poruka ispod će se odmah ažurirati. To se dešava jer setItems
pokreće ponovni render, a items.find(...)
će pronaći stavku sa ažuriranim nazivom. Nije vam potrebno da čuvate izabranu stavku u state-u, zato što je jedino izabrani ID obavezan. Ostatak može biti izračunat tokom rendera.
Izbegavati duboko ugnježedeni state
Zamislite plan putovanja koji sadrži planete, kontinente i države. Možete biti u iskušenju da strukturirate state upotrebom ugnježdenih objekata i nizova, kao u ovom primeru:
export const initialTravelPlan = { id: 0, title: '(Root)', childPlaces: [{ id: 1, title: 'Zemlja', childPlaces: [{ id: 2, title: 'Afrika', childPlaces: [{ id: 3, title: 'Bocvana', childPlaces: [] }, { id: 4, title: 'Egipat', childPlaces: [] }, { id: 5, title: 'Kenija', childPlaces: [] }, { id: 6, title: 'Madagaskar', childPlaces: [] }, { id: 7, title: 'Maroko', childPlaces: [] }, { id: 8, title: 'Nigerija', childPlaces: [] }, { id: 9, title: 'Južna Afrika', childPlaces: [] }] }, { id: 10, title: 'Amerika', childPlaces: [{ id: 11, title: 'Argentina', childPlaces: [] }, { id: 12, title: 'Brazil', childPlaces: [] }, { id: 13, title: 'Barbados', childPlaces: [] }, { id: 14, title: 'Kanada', childPlaces: [] }, { id: 15, title: 'Jamajka', childPlaces: [] }, { id: 16, title: 'Meksiko', childPlaces: [] }, { id: 17, title: 'Trinidad i Tobago', childPlaces: [] }, { id: 18, title: 'Venecuela', childPlaces: [] }] }, { id: 19, title: 'Azija', childPlaces: [{ id: 20, title: 'Kina', childPlaces: [] }, { id: 21, title: 'Indija', childPlaces: [] }, { id: 22, title: 'Singapur', childPlaces: [] }, { id: 23, title: 'Južna Koreja', childPlaces: [] }, { id: 24, title: 'Tajland', childPlaces: [] }, { id: 25, title: 'Vijetnam', childPlaces: [] }] }, { id: 26, title: 'Evropa', childPlaces: [{ id: 27, title: 'Hrvatska', childPlaces: [], }, { id: 28, title: 'Francuska', childPlaces: [], }, { id: 29, title: 'Nemačka', childPlaces: [], }, { id: 30, title: 'Italija', childPlaces: [], }, { id: 31, title: 'Portugal', childPlaces: [], }, { id: 32, title: 'Španija', childPlaces: [], }, { id: 33, title: 'Turska', childPlaces: [], }] }, { id: 34, title: 'Okeanija', childPlaces: [{ id: 35, title: 'Australija', childPlaces: [], }, { id: 36, title: 'Bora Bora (Francuska Polinezija)', childPlaces: [], }, { id: 37, title: 'Uskršnje ostrvo (Čile)', childPlaces: [], }, { id: 38, title: 'Fidži', childPlaces: [], }, { id: 39, title: 'Havaji (SAD)', childPlaces: [], }, { id: 40, title: 'Novi Zeland', childPlaces: [], }, { id: 41, title: 'Vanuatu', childPlaces: [], }] }] }, { id: 42, title: 'Mesec', childPlaces: [{ id: 43, title: 'Rheita krater', childPlaces: [] }, { id: 44, title: 'Piccolomini krater', childPlaces: [] }, { id: 45, title: 'Tihov krater', childPlaces: [] }] }, { id: 46, title: 'Mars', childPlaces: [{ id: 47, title: 'Corn Town', childPlaces: [] }, { id: 48, title: 'Green Hill', childPlaces: [] }] }] };
Recimo da želite dodati dugme za brisanje mesta koje ste već posetili. Kako biste to uradili? Ažuriranje ugnježdenog state-a podrazumeva pravljenje kopija svih objekata od mesta koje se promenilo na gore. Brisanje duboko ugnježdenog mesta bi zahtevalo kopiranje celokupnog lanca roditeljskih objekata. Takav kod može biti veoma opširan.
Ako je state previše ugnježden da bi se lako ažurirao, razmotrite da ga napravite da bude “flat”. Ovde je jedan način da restrukturirate ove podatke. Umesto strukture nalik na stablo gde svaki place
ima niz dečjih mesta, možete napraviti da svako mesto čuva niz ID-eva dečjih mesta. Onda napravite mapiranje od ID-a mesta do odgovarajućeg mesta.
Ovo restrukturiranje podataka vas može podsetiti na tabelu u bazi podataka:
export const initialTravelPlan = { 0: { id: 0, title: '(Root)', childIds: [1, 42, 46], }, 1: { id: 1, title: 'Zemlja', childIds: [2, 10, 19, 26, 34] }, 2: { id: 2, title: 'Afrika', childIds: [3, 4, 5, 6 , 7, 8, 9] }, 3: { id: 3, title: 'Bocvana', childIds: [] }, 4: { id: 4, title: 'Egipat', childIds: [] }, 5: { id: 5, title: 'Kenija', childIds: [] }, 6: { id: 6, title: 'Madagaskar', childIds: [] }, 7: { id: 7, title: 'Maroko', childIds: [] }, 8: { id: 8, title: 'Nigerija', childIds: [] }, 9: { id: 9, title: 'Južna Afrika', childIds: [] }, 10: { id: 10, title: 'Amerika', childIds: [11, 12, 13, 14, 15, 16, 17, 18], }, 11: { id: 11, title: 'Argentina', childIds: [] }, 12: { id: 12, title: 'Brazil', childIds: [] }, 13: { id: 13, title: 'Barbados', childIds: [] }, 14: { id: 14, title: 'Kanada', childIds: [] }, 15: { id: 15, title: 'Jamajka', childIds: [] }, 16: { id: 16, title: 'Meksiko', childIds: [] }, 17: { id: 17, title: 'Trinidad i Tobago', childIds: [] }, 18: { id: 18, title: 'Venecuela', childIds: [] }, 19: { id: 19, title: 'Azija', childIds: [20, 21, 22, 23, 24, 25], }, 20: { id: 20, title: 'Kina', childIds: [] }, 21: { id: 21, title: 'Indija', childIds: [] }, 22: { id: 22, title: 'Singapur', childIds: [] }, 23: { id: 23, title: 'Južna Koreja', childIds: [] }, 24: { id: 24, title: 'Tajland', childIds: [] }, 25: { id: 25, title: 'Vijetnam', childIds: [] }, 26: { id: 26, title: 'Evropa', childIds: [27, 28, 29, 30, 31, 32, 33], }, 27: { id: 27, title: 'Hrvatska', childIds: [] }, 28: { id: 28, title: 'Francuska', childIds: [] }, 29: { id: 29, title: 'Nemačka', childIds: [] }, 30: { id: 30, title: 'Italija', childIds: [] }, 31: { id: 31, title: 'Portugal', childIds: [] }, 32: { id: 32, title: 'Španija', childIds: [] }, 33: { id: 33, title: 'Turska', childIds: [] }, 34: { id: 34, title: 'Okeanija', childIds: [35, 36, 37, 38, 39, 40, 41], }, 35: { id: 35, title: 'Australija', childIds: [] }, 36: { id: 36, title: 'Bora Bora (Francuska Polinezija)', childIds: [] }, 37: { id: 37, title: 'Uskršnje ostrvo (Čile)', childIds: [] }, 38: { id: 38, title: 'Fidži', childIds: [] }, 39: { id: 40, title: 'Havaji (SAD)', childIds: [] }, 40: { id: 40, title: 'Novi Zeland', childIds: [] }, 41: { id: 41, title: 'Vanuatu', childIds: [] }, 42: { id: 42, title: 'Mesec', childIds: [43, 44, 45] }, 43: { id: 43, title: 'Rheita krater', childIds: [] }, 44: { id: 44, title: 'Piccolomini krater', childIds: [] }, 45: { id: 45, title: 'Tihov krater', childIds: [] }, 46: { id: 46, title: 'Mars', childIds: [47, 48] }, 47: { id: 47, title: 'Corn Town', childIds: [] }, 48: { id: 48, title: 'Green Hill', childIds: [] } };
Sada, kada je state “flat” (poznato i pod nazivom “normalizovano”), ažuriranje ugnježdenih stavki postaje lakše.
Da biste uklonili mesto, potrebno je ažurirati dva nivoa state-a:
- Ažurirana verzija njegovog roditeljskog mesta treba da isključi uklonjeni ID iz
childIds
niza. - Ažurirana verzija root “tabela” objekta treba da uključi ažuriranu verziju roditeljskog mesta.
Ovde je primer kako to možete uraditi:
import { useState } from 'react'; import { initialTravelPlan } from './places.js'; export default function TravelPlan() { const [plan, setPlan] = useState(initialTravelPlan); function handleComplete(parentId, childId) { const parent = plan[parentId]; // Kreiraj novu verziju roditeljskog mesta // koji ne sadrži ovaj ID deteta. const nextParent = { ...parent, childIds: parent.childIds .filter(id => id !== childId) }; // Ažuriraj root state objekat... setPlan({ ...plan, // ...tako da ima ažuriranog roditelja. [parentId]: nextParent }); } const root = plan[0]; const planetIds = root.childIds; return ( <> <h2>Mesta za posetu</h2> <ol> {planetIds.map(id => ( <PlaceTree key={id} id={id} parentId={0} placesById={plan} onComplete={handleComplete} /> ))} </ol> </> ); } function PlaceTree({ id, parentId, placesById, onComplete }) { const place = placesById[id]; const childIds = place.childIds; return ( <li> {place.title} <button onClick={() => { onComplete(parentId, id); }}> Kompetiraj </button> {childIds.length > 0 && <ol> {childIds.map(childId => ( <PlaceTree key={childId} id={childId} parentId={id} placesById={placesById} onComplete={onComplete} /> ))} </ol> } </li> ); }
Možete ugnjezditi state koliko god želite, ali pravljenje “flat” state-a može rešiti dosta problema. Čini da ažuriranje state-a bude lakše, a takođe i osigurava da nemate dupliranje u različitim delovima ugnježdenog objekta.
Deep Dive
Idealno, takođe bi uklanjali obrisane stavke (i njihovu decu!) iz “tabela” objekta da biste poboljšali upotrebu memorije. Ova verzija to radi. Takođe koristi Immer kako bi učinila logiku ažuriranja konciznijom.
{ "dependencies": { "immer": "1.7.3", "react": "latest", "react-dom": "latest", "react-scripts": "latest", "use-immer": "0.5.1" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "devDependencies": {} }
Ponekad, možete smanjiti ugnježdenost state-a pomeranjem nekih ugnježdenih objekata u dečje komponente. Ovo radi dobro za kratkotrajne UI state-ove koji ne moraju biti čuvani, kao što je podatak da li se prelazi mišem iznad neke stavke.
Recap
- Ako se dve state promenljive uvek istovremeno ažuriraju, pokušajte da ih spojite u jednu.
- Pažljivo birajte state promenljive da biste izbegli kreiranje “nemogućih” stanja.
- Strukturirajte state na način koji smanjuje šanse za pravljenje grešaka prilikom ažuriranja.
- Izbegavajte suvišan i dupliran state kako ne biste morali da ih sinhronizujete.
- Ne stavljajte props unutar state-a osim ako specifično želite da sprečite ažuriranja.
- Za UI šablone poput odabira, u state-u čuvajte ID ili indeks umesto celog objekta.
- Ako je ažuriranje duboko ugnježdenog state-a komplikovano, probajte da ga flatten-ujete.
Izazov 1 od 4: Popraviti komponentu koja se ne ažurira
Ova Clock
komponenta prima dva props-a: color
i time
. Kada izaberete drugu boju iz dropdown-a, Clock
komponenta primi drugačiji color
prop iz svoje roditeljske komponente. Međutim, iz nekog razloga, prikazana boja se ne ažurira. Zašto? Popravite problem.
import { useState } from 'react'; export default function Clock(props) { const [color, setColor] = useState(props.color); return ( <h1 style={{ color: color }}> {props.time} </h1> ); }