State Yapısını Seçme
Bir bileşenin state’ini iyi yapılandırmak, değişiklik yapılması ve hata ayıklaması kolay bir bileşen ile sürekli hatalara sebep olan bir bileşen arasındaki farkı yaratabilir. Bu sayfada state’i yapılandırırken göz önünde bulundurmanız gereken ipuçlarını bulabilirsiniz.
Bunları öğreneceksiniz
- Ne zaman tek veya birden çok state değişkeni kullanmalısınız
- State’i düzenlerken nelerden kaçınılmalıdır
- State yapısındaki yaygın sorunlar nasıl giderilir
State’i yapılandırmanın prensipleri
İçinde state barındıran bir bileşen yazdığınızda, bu bileşende kaç tane state değişkeni olması gerektiği veya bu değişkenlerin verilerini nasıl şekillendirmeniz gerektiği hakkında kararlar vermeniz gerekecektir. Optimalden uzak state yapısıyla dahi doğru şekilde çalışan programlar yazabilmeniz mümkün olsa da, sizi daha iyi kararlar vermeye yönlendirecek bazı prensipler aşağıdaki gibidir:
- Bağlantılı state değişkenlerini gruplayın. Eğer birden fazla state değişkenini hep aynı anda güncelliyorsanız, bu değişkenleri tek bir state değişkeninde birleştirmeyi değerlendirin.
- State çelişkilerinden kaçının. Eğer bir bileşenin state değişkenleri birbiriyle çelişecek ya da uyuşmayacak şekilde tasarlanırsa hatalara açık kapı bırakmış olursunuz. Bu durumdan kaçının.
- Gereksiz state oluşturmaktan kaçının. Eğer bileşeni render ederken, bileşenin prop’larından ya da varolan state değişkenlerinden ihtiyacınız olan bilgiyi hesaplayabiliyorsanız bu bilgiyi bileşenin state’inde tutmamalısınız.
- Yinelenen state değişkenlerinden kaçının. Aynı veri, farklı state değişkenleri ya da iç içe nesneler içinde yinelendiğinde senkronizasyonu sağlamak zorlaşabilir. Buna benzer tekrarları en aza indirgemeye çalışın.
- Derinlemesine iç içe olan bir state yapısından kaçının. Derinlemesine hiyerarşik yapıya sahip olan state’i güncellemek çok da pratik bir değildir. State’i daha düz bir yapıda şekillendirmeye çalışın.
Bu prensiplerin asıl amacı, state’i hatalara sebep olmadan kolayca güncelleyebilmektir. State’ten gereksiz ve yinelenen veriyi temizlemek tüm parçaların birbiriyle senkronize kalmasına yardımcı olur. Bu durum, bir veritabanı mühendisinin hata ihtimalini azaltmak için veritabanı yapısını “normalleştirmek” istemesine benzetilebilir. Albert Einstein’ın sözleriyle yorumlamak gerekirse, “State’i olabildiğince basit hale getirin; ama çok da basit olmasın.”
Haydi şimdi bu prensiplerin nasıl uygulandığını görelim.
Bağlantılı state değişkenlerini gruplayın
Bazen tek bir state değişkeni mi yoksa birden fazla state değişkeni mi kullanmanız gerektiği hakkında emin olmayabilirsiniz.
Bu şekilde mi yapmalısınız?
const [x, setX] = useState(0);
const [y, setY] = useState(0);
Yoksa bu şekilde mi?
const [position, setPosition] = useState({ x: 0, y: 0 });
Teknik olarak ikisini de kullanabilirsiniz. Fakat iki farklı state değişkeni hep aynı anda değişiyorsa bunları tek bir state değişkeninde birleştirmek iyi bir fikir olabilir. Böylece, imleci oynatmanın her iki koordinatı da tek seferde güncellediği örnekte olduğu gibi onları sürekli senkronize tutmayı unutmazsınız:
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> ) }
Veriyi bir nesnede ya da dizide gruplayacağınız diğer bir örnek ise kaç farklı state parçasına ihtiyacınız olacağını bilemediğiniz durumlardır. Örneğin, kullanıcının özel alanlar ekleyebildiği bir form oluşturmanız gerektiğinde bu prensip size yardımcı olacaktır.
State çelişkilerinden kaçının
İşte isSending
ve isSent
state değişkenlerine sahip olan bir otel geri bildirim formu:
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>Thanks for feedback!</h1> } return ( <form onSubmit={handleSubmit}> <p>How was your stay at The Prancing Pony?</p> <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <br /> <button disabled={isSending} type="submit" > Send </button> {isSending && <p>Sending...</p>} </form> ); } // Mesajı gönderiyormuş gibi yap. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }
Bu kod her ne kadar çalışıyor olsa da “imkansız” state’lere açık kapı bırakıyor. Örneğin, setIsSent
ve setIsSending
’i birlikte çağırmayı unutursanız, isSending
ve isSent
değişkenlerinin aynı anda true
olduğu bir durumla karşı karşıya kalabilirsiniz. Bileşeniniz ne kadar karmaşık olursa neler olduğunu anlamanız da aynı oranda zorlaşacaktır.
isSending
ve isSent
değişkenlerinin hiçbir zaman aynı anda true
olmaması gerektiğinden bunları 'typing'
(başlangıç değeri), 'sending'
ve 'sent'
durumlarından birini alabilen status
adında tek bir state değişkenine dönüştürmek daha iyi olur:
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>Thanks for feedback!</h1> } return ( <form onSubmit={handleSubmit}> <p>How was your stay at The Prancing Pony?</p> <textarea disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <br /> <button disabled={isSending} type="submit" > Send </button> {isSending && <p>Sending...</p>} </form> ); } // Mesajı gönderiyormuş gibi yap. function sendMessage(text) { return new Promise(resolve => { setTimeout(resolve, 2000); }); }
Okunabilirlik için hala sabit değişkenler oluşturabilirsiniz.
const isSending = status === 'sending';
const isSent = status === 'sent';
Fakat onlar state değişkenleri olmadığı için birbirleriyle senkronize kalıp kalmamaları hakkında endişelenmenize gerek yok.
Gereksiz state oluşturmaktan kaçının
Eğer bileşeni render ederken bileşenin prop’larından ya da varolan state değişkenlerinden ihtiyacınız olan bilgiyi hesaplayabiliyorsanız bu bilgiyi bileşenin state’inde tutmamalısınız.
Örneğin bu formu ele alın. Bu form çalışıyor fakat içinde gereksiz bir state bulabilir misiniz?
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>Let’s check you in</h2> <label> First name:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Last name:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Your ticket will be issued to: <b>{fullName}</b> </p> </> ); }
Bu form üç state değişkeni içeriyor: firstName
, lastName
ve fullName
. Fakat fullName
gereksiz bir state değişkeni. fullName
değişkeninin değerini her zaman firstName
ve lastName
değişkenlerinin değerlerini kullanarak render esnasında hesaplayabilirsiniz, bu yüzden state’ten kaldırmalısınız.
Bu işlemi şu şekilde yapabilirsiniz:
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>Let’s check you in</h2> <label> First name:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Last name:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Your ticket will be issued to: <b>{fullName}</b> </p> </> ); }
Burada fullName
bir state değişkeni değil. Bunun yerine bileşen render edilirken hesaplanıyor:
const fullName = firstName + ' ' + lastName;
Sonuç olarak, değişiklik yöneticilerinin bu değeri güncellemek için özel bir şey yapmasına gerek kalmıyor. setFirstName
ve setLastName
fonksiyonlarını çağırdığınızda bileşenin tekrar render edilmesini tetiklemiş olursunuz, sonrasında da fullName
değişkeninin yeni değeri güncel veri kullanılarak hesaplanacaktır.
Derinlemesine İnceleme
Gereksiz state’in yaygın bir örneği aşağıdaki gibidir:
function Message({ messageColor }) {
const [color, setColor] = useState(messageColor);
Bu örnekte color
, messageColor
prop’u ile başlangıç değeri tanımlanan bir state değişkeni. Sorun şu ki üst bileşen daha sonra farklı bir messageColor
değeri (örneğin, 'blue'
yerine 'red'
) gönderdiğinde color
state değişkeni güncellenmeyecek! Çünkü state yalnızca ilk render esnasında başlangıç değerini alır.
Bu sebeple bir prop’u state değişkenine “yansıtmak” kafa karışıklığına sebep olabilir. Bunun yerine, messageColor
prop’unu direkt olarak kod içinde kullanın. Eğer daha kısa bir isim vermek istiyorsanız sabit değişken kullanın:
function Message({ messageColor }) {
const color = messageColor;
Böylece üst bileşenden gönderilen prop ile senkronizasyon sorunu oluşmayacaktır.
Prop’ları state’e “yansıtmak”, yalnızca belirli bir prop’a ait tüm güncellemeleri yok saymak istediğinizde mantıklı bir kullanımdır. Bu durumda, genel kabul olarak, gönderilecek yeni değerlerin yok sayıldığını belirtmek için prop adını initial
veya default
ön ekiyle başlatın:
function Message({ initialColor }) {
// `color` state değişkeni `initialColor`'ın *ilk* değerini tutar.
// `initialColor`'a ait sonraki tüm değişiklikler yok sayılır.
const [color, setColor] = useState(initialColor);
Yinelenen state değişkenlerinden kaçının
Aşağıdaki menü listesi bileşeni birden fazla atıştırmalık içinden birini seçmenizi sağlar:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', id: 2 }, ]; export default function Menu() { const [items, setItems] = useState(initialItems); const [selectedItem, setSelectedItem] = useState( items[0] ); return ( <> <h2>What's your travel snack?</h2> <ul> {items.map(item => ( <li key={item.id}> {item.title} {' '} <button onClick={() => { setSelectedItem(item); }}>Choose</button> </li> ))} </ul> <p>You picked {selectedItem.title}.</p> </> ); }
Mevcut durumda, seçilen öğe selectedItem
state değişkeninde nesne olarak saklanır. Ancak bu durum pek de iyi değil çünkü selectedItem
’ın içeriği items
listesinde tutulan nesne ile aynı. Bu durum, öğe hakkındaki bilginin iki farklı yerde yinelenerek tutulduğu anlamına gelir.
Peki bu neden bir sorun? Haydi her öğeyi düzenlenebilir yapalım:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', 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>What's your travel snack?</h2> <ul> {items.map((item, index) => ( <li key={item.id}> <input value={item.title} onChange={e => { handleItemChange(item.id, e) }} /> {' '} <button onClick={() => { setSelectedItem(item); }}>Choose</button> </li> ))} </ul> <p>You picked {selectedItem.title}.</p> </> ); }
Dikkat ederseniz önce bir öğeye ait “Choose” butonuna tıklar ve sonrasında o öğeyi düzenlerseniz girdi güncellenecektir fakat aşağıdaki öğe adı yapılan değişikliği yansıtmayacaktır. Bu yinelenen bir state’e sahip olmanızdan kaynaklanır ve selectedItem
’ı güncellemeyi unuttuğunuz anlamına gelir.
selectedItem
’ı güncelleyebilecek olsanız dahi, daha basit bir çözüm yinelemeyi ortadan kaldırmaktır. Bu örnekte selectedItem
nesnesi (items
içindeki nesnelerle yineleme durumunu yaratan nesne) yerine state’te selectedId
değerini tutup sonrasında items
dizisinde bu ID’ye sahip olan selectedItem
’ı bulabilirsiniz:
import { useState } from 'react'; const initialItems = [ { title: 'pretzels', id: 0 }, { title: 'crispy seaweed', id: 1 }, { title: 'granola bar', 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>What's your travel snack?</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); }}>Choose</button> </li> ))} </ul> <p>You picked {selectedItem.title}.</p> </> ); }
(Alternatif olarak seçilen indeksi state’te tutabilirsiniz.)
State aşağıdaki gibi yineleniyordu:
items = [{ id: 0, title: 'pretzels'}, ...]
selectedItem = {id: 0, title: 'pretzels'}
Fakat değişiklik sonrasında aşağıdaki gibi oldu:
items = [{ id: 0, title: 'pretzels'}, ...]
selectedId = 0
Yineleme durumu ortadan kalktı ve yalnızca gerekli olan state’i tutuyorsunuz!
Artık seçili öğeyi düzenlediğinizde altındaki mesaj anında güncellenecektir. Bunun sebebi setItems
’ın bileşenin tekrar render edilmesine sebep olması ve items.find(...)
’ın başlığı güncellenen öğeyi bulmasıdır. Yani seçili öğeyi tutmanıza gerek yoktur çünkü sadece seçili ID gereklidir. Geri kalan bilgi render esnasında hesaplanabilir.
Derinlemesine iç içe olan bir state yapısından kaçının
Gezegenleri, kıtaları ve ülkeleri içeren bir seyahat planı hayal edin. Bu durumda state yapısını, iç içe nesne ve dizilerden oluşacak şekilde tasarlamak sizi cezbedebilir:
export const initialTravelPlan = { id: 0, title: '(Root)', childPlaces: [{ id: 1, title: 'Earth', childPlaces: [{ id: 2, title: 'Africa', childPlaces: [{ id: 3, title: 'Botswana', childPlaces: [] }, { id: 4, title: 'Egypt', childPlaces: [] }, { id: 5, title: 'Kenya', childPlaces: [] }, { id: 6, title: 'Madagascar', childPlaces: [] }, { id: 7, title: 'Morocco', childPlaces: [] }, { id: 8, title: 'Nigeria', childPlaces: [] }, { id: 9, title: 'South Africa', childPlaces: [] }] }, { id: 10, title: 'Americas', childPlaces: [{ id: 11, title: 'Argentina', childPlaces: [] }, { id: 12, title: 'Brazil', childPlaces: [] }, { id: 13, title: 'Barbados', childPlaces: [] }, { id: 14, title: 'Canada', childPlaces: [] }, { id: 15, title: 'Jamaica', childPlaces: [] }, { id: 16, title: 'Mexico', childPlaces: [] }, { id: 17, title: 'Trinidad and Tobago', childPlaces: [] }, { id: 18, title: 'Venezuela', childPlaces: [] }] }, { id: 19, title: 'Asia', childPlaces: [{ id: 20, title: 'China', childPlaces: [] }, { id: 21, title: 'India', childPlaces: [] }, { id: 22, title: 'Singapore', childPlaces: [] }, { id: 23, title: 'South Korea', childPlaces: [] }, { id: 24, title: 'Thailand', childPlaces: [] }, { id: 25, title: 'Vietnam', childPlaces: [] }] }, { id: 26, title: 'Europe', childPlaces: [{ id: 27, title: 'Croatia', childPlaces: [], }, { id: 28, title: 'France', childPlaces: [], }, { id: 29, title: 'Germany', childPlaces: [], }, { id: 30, title: 'Italy', childPlaces: [], }, { id: 31, title: 'Portugal', childPlaces: [], }, { id: 32, title: 'Spain', childPlaces: [], }, { id: 33, title: 'Turkey', childPlaces: [], }] }, { id: 34, title: 'Oceania', childPlaces: [{ id: 35, title: 'Australia', childPlaces: [], }, { id: 36, title: 'Bora Bora (French Polynesia)', childPlaces: [], }, { id: 37, title: 'Easter Island (Chile)', childPlaces: [], }, { id: 38, title: 'Fiji', childPlaces: [], }, { id: 39, title: 'Hawaii (the USA)', childPlaces: [], }, { id: 40, title: 'New Zealand', childPlaces: [], }, { id: 41, title: 'Vanuatu', childPlaces: [], }] }] }, { id: 42, title: 'Moon', childPlaces: [{ id: 43, title: 'Rheita', childPlaces: [] }, { id: 44, title: 'Piccolomini', childPlaces: [] }, { id: 45, title: 'Tycho', childPlaces: [] }] }, { id: 46, title: 'Mars', childPlaces: [{ id: 47, title: 'Corn Town', childPlaces: [] }, { id: 48, title: 'Green Hill', childPlaces: [] }] }] };
Hali hazırda ziyaret ettiğiniz bir lokasyonu silmeye yarayan bir buton eklemek istediğinizi varsayalım. Nasıl yapardınız? İç içe yapılandırılmış state’i güncellemek için değiştiği yere kadar olan tüm nesnelerin kopyalarını yaratmak gerekir. Derinlemesine iç içe geçmiş bir lokasyonu silmek için ise tüm üst lokasyon zincirini kopyalamak gerekir. Böyle bir kod gereğinden daha uzun olabilir.
Eğer state güncellemek için gereğinden daha fazla iç içe geçmiş bir halde ise onu daha düz bir yapıya getirmeyi değerlendirin. İşte bu veriyi yeniden yapılandırmanız için bir yol. Eğer her place
(lokasyon) alt lokasyonları için ağaç yapısında bir diziye sahipse her place
’in (lokasyonun) alt lokasyonlarının IDlerini tuttuğu bir diziye sahip olmasını sağlayabilirsiniz. Sonrasında da her bir lokasyon ID’sine karşılık gelen lokasyonun eşlemesini tutabilirsiniz.
Veriyi aşağıdaki gibi yeniden yapılandırmak size bir veritabanı tablosunu hatırlatabilir:
export const initialTravelPlan = { 0: { id: 0, title: '(Root)', childIds: [1, 42, 46], }, 1: { id: 1, title: 'Earth', childIds: [2, 10, 19, 26, 34] }, 2: { id: 2, title: 'Africa', childIds: [3, 4, 5, 6 , 7, 8, 9] }, 3: { id: 3, title: 'Botswana', childIds: [] }, 4: { id: 4, title: 'Egypt', childIds: [] }, 5: { id: 5, title: 'Kenya', childIds: [] }, 6: { id: 6, title: 'Madagascar', childIds: [] }, 7: { id: 7, title: 'Morocco', childIds: [] }, 8: { id: 8, title: 'Nigeria', childIds: [] }, 9: { id: 9, title: 'South Africa', childIds: [] }, 10: { id: 10, title: 'Americas', 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: 'Canada', childIds: [] }, 15: { id: 15, title: 'Jamaica', childIds: [] }, 16: { id: 16, title: 'Mexico', childIds: [] }, 17: { id: 17, title: 'Trinidad and Tobago', childIds: [] }, 18: { id: 18, title: 'Venezuela', childIds: [] }, 19: { id: 19, title: 'Asia', childIds: [20, 21, 22, 23, 24, 25], }, 20: { id: 20, title: 'China', childIds: [] }, 21: { id: 21, title: 'India', childIds: [] }, 22: { id: 22, title: 'Singapore', childIds: [] }, 23: { id: 23, title: 'South Korea', childIds: [] }, 24: { id: 24, title: 'Thailand', childIds: [] }, 25: { id: 25, title: 'Vietnam', childIds: [] }, 26: { id: 26, title: 'Europe', childIds: [27, 28, 29, 30, 31, 32, 33], }, 27: { id: 27, title: 'Croatia', childIds: [] }, 28: { id: 28, title: 'France', childIds: [] }, 29: { id: 29, title: 'Germany', childIds: [] }, 30: { id: 30, title: 'Italy', childIds: [] }, 31: { id: 31, title: 'Portugal', childIds: [] }, 32: { id: 32, title: 'Spain', childIds: [] }, 33: { id: 33, title: 'Turkey', childIds: [] }, 34: { id: 34, title: 'Oceania', childIds: [35, 36, 37, 38, 39, 40, 41], }, 35: { id: 35, title: 'Australia', childIds: [] }, 36: { id: 36, title: 'Bora Bora (French Polynesia)', childIds: [] }, 37: { id: 37, title: 'Easter Island (Chile)', childIds: [] }, 38: { id: 38, title: 'Fiji', childIds: [] }, 39: { id: 40, title: 'Hawaii (the USA)', childIds: [] }, 40: { id: 40, title: 'New Zealand', childIds: [] }, 41: { id: 41, title: 'Vanuatu', childIds: [] }, 42: { id: 42, title: 'Moon', childIds: [43, 44, 45] }, 43: { id: 43, title: 'Rheita', childIds: [] }, 44: { id: 44, title: 'Piccolomini', childIds: [] }, 45: { id: 45, title: 'Tycho', childIds: [] }, 46: { id: 46, title: 'Mars', childIds: [47, 48] }, 47: { id: 47, title: 'Corn Town', childIds: [] }, 48: { id: 48, title: 'Green Hill', childIds: [] } };
Artık state “düz” (“normalleştirilmiş” olarak da bilinir), iç içe öğeleri güncellemek daha kolay.
Artık bir lokasyonu kaldırmak için state’in yalnızca iki seviyesini güncellemeniz gerekiyor.
- Üst lokasyonunun güncellenmiş sürümü kaldırılan ID’yi
childIds
dizisinden çıkarmalı. - Kök “tablo” nesnesinin güncellenmiş sürümü üst lokasyonun güncellenmiş halini içermeli.
İşte bunu nasıl yapabileceğinizin bir örneği:
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]; // Üst lokasyonun, bu alt ID'yi içermeyen // yeni bir sürümünü oluştur const nextParent = { ...parent, childIds: parent.childIds .filter(id => id !== childId) }; // Kök state nesnesini güncelle... setPlan({ ...plan, // ...böylece güncellenen üst öğeye sahip olsun. [parentId]: nextParent }); } const root = plan[0]; const planetIds = root.childIds; return ( <> <h2>Places to visit</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); }}> Complete </button> {childIds.length > 0 && <ol> {childIds.map(childId => ( <PlaceTree key={childId} id={childId} parentId={id} placesById={placesById} onComplete={onComplete} /> ))} </ol> } </li> ); }
State’i istediğiniz kadar iç içe olacak şekilde yapılandırabilirsiniz. Fakat “düz” yapılandırmak birçok sorunu çözebilir. Hem state’i güncellemeyi kolaylaştırır hem de iç içe nesnenin farklı bölümlerinde yinelenme ihtimalini ortadan kaldırmaya yardımcı olur.
Derinlemesine İnceleme
İdeal olarak, bellek kullanımını iyileştirmek için silinen öğeleri (ve alt öğelerini!) “tablo” nesnesinden kaldırmalısınız. Bu sürüm bunu gerçekleştirir. Aynı zamanda güncelleme mantığını daha kısa ve öz hale getirmek için Immer kullanır.
{ "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": {} }
Bazı durumlarda iç içe state’in bazı parçalarını alt bileşenlere taşıyarak state’i iç içe yapılandırmayı azaltabilirsiniz. Bu durum çok kısa süren, saklanması gerekmeyen, UI state’lerinde işe yarar, örneğin bir öğenin üzerine gelindiğinde (hover edildiğinde).
Özet
- Eğer iki farklı state değişkeni hep aynı anda güncelleniyorsa bunları tek bir state değişkeninde birleştirmeyi değerlendirin.
- ”İmkansız” state’ler oluşturmaktan kaçınmak için state değişkenlerini dikkatle seçin.
- State’inizi, onu güncellerken hata yapma ihtimalini en aza indirgeyecek şekilde yapılandırın.
- State’inizi senkronize tutmaya çalışmak yerine gereksiz ve yinelenen şekilde yapılandırmaktan kaçının.
- Güncellemeleri engellemek istediğiniz durumlar haricinde prop’ları state içine koymayın.
- Seçim (selection) gibi UI kalıpları için state’te objenin kendisi yerine IDsini ya da indeksini tutun.
- Eğer derinlemesine iç içe geçmiş state’i güncellemek karmaşıksa onu düz bir yapıya getirmeye çalışın.
Problem 1 / 4: Güncellenmeyen bir bileşeni düzeltin
Clock
bileşeni iki prop alır: color
ve time
. Seçim kutusunda farklı bir renk seçtiğinizde, Clock
bileşeni üst bileşeninden farklı bir color
prop’u alır. Fakat, bir sebepten ötürü gösterilen renk güncellenmiyor. Neden? Bu sorunu çözün.
import { useState } from 'react'; export default function Clock(props) { const [color, setColor] = useState(props.color); return ( <h1 style={{ color: color }}> {props.time} </h1> ); }