Merhabalar, bugün Einstein’in o meşhur sözüne ithafen bir yazıya başlayacağım.
“Bir şeyi 6 yaşındaki bir çocuğa anlatamıyorsanız, siz de anlamamışsınız demektir.”
Bu durumda, useReducer ’in ne olduğunu karıştıran ben, size öyle bir anlatacağım ki çoook basit bir şekilde kavramış olacaksınız.
Daha doğrusu ben kendi kendime anlatacağım. Amacım, kendim tam detayıyla öğrenmek 😅
ee ne demişler. “Öğretmek 2 Kere Öğrenmektir.”
Bu anlatımda kullandığım bir kaynak var.
Youtube’da Lama Dev olarak bilinen Şafak Kocaoğlu’nun kanalı. Türkçe biliyor mu emin değilim çünkü sadece İngilizce içerikler üretiyor. Neyse sağ olsun, İngilizce dahil olmak üzere muhteşem bir içerik üretmiş.
İzledim ve kafamdaki bütün soru işaretleri gitti. Şimdi o kaynağı Türkçe olarak çevireceğim ve basit bir şekilde size anlatacağım.
Başlayalım, öncelikle useState nedir?
Neden useState? Çünkü useReducer ’ı iyi anlamak için önce useState’i de anlamak gerekiyor.
16.8 sürümüyle birlikte gelen bir özellik. Normalde Class yapısında şöyle bir kod dizaynı kullanıyorduk:
Example adında bir Sınıf (Class) tanımlanmış ve bu sınıf React.Component ’ten üretilmiş.
Onun altında bir constructor ( props ) {} bulunuyor.
props = Properties …
Burada şöyle bir handikap var. Eğer siz sınıf içerisinde state ya da bind() fonksiyonu kullanmayacaksanız. constructor(props) eklemenize gerek yok.
Biz kullanacağımız için ekliyoruz. Daha sonra constructor(props)’u kullanabilmemiz için super(props) çağırmamız gerekiyor.
Peki super(props) nedir?
super(props) Javascript’te ana class’a referans verir. constructor ’dan sonra kullanılmayan super(props) için biz this kullanamayız.
Diğer bir deyişle, constructor ’unuz var ve super(props) eklemediniz. Bu durumda Javascript sizin this.state olarak çağırmanıza izin vermeyecek.
Devam Edelim…
State = durum demek.
Diğer bir bakışla, bir durum değişkeni yaratmış oluyoruz.
Bu değişkenimiz bir obje ve içerisinde count barındırılıyor.
Count’da 0’a eşitlenmiş.
Tamamdır buraya kadar anlaştık 🙌
Şimdi sıra bu count’ı nasıl değiştirdiğimizde…
Göreceğin üzere, constructor ’umuzdan sonra bir render() {} oluşturulmuş.
Bunu Functional Component ’de (Fonksiyonel Bileşenler) görmen mümkün değil. Çünkü orada render() fonksiyonu kullanılmıyor. render ’dan sonraki return() bizim için yeterli.
Html yapılarını anlatmaya gerek yok. Sadece <p></p> etiketi içerisinde bir {this.state.count} döndürülmüş. Bu şu anki count ’ımızın değerini bize yazdıracak. {}’ lar html etiketi içerisinde javascript kodu çalıştırmamız için kullanılıyor.
Geldik önemli yere:
onClick içerisinde setState fonksiyonunun çağırılması sağlanmış.
bu fonksiyonun amacı count sayısını her click edildiğinde +1 arttırmak.
setState bizim state ’imizi güncellememiz için çağrılan fonksiyondur.
// Özetle…
React içerisinde verilerimizi tuttuğumuz ya da componentler arasında veri aktarımı yapmamız için state ’ler aracılığıyla verileri depolamamız gerekiyor. Bu bir array olabilir bir değişken içerisine olabilir, bir sayı olabilir. Bu verileri aktarmanın güzel yanı da görüldüğü üzere state hook ’unu kullanmak.
Bunu son olarak Fonksiyonel Bileşenler (Functional Component) üzerinde de gösterip useReducer’a geçeceğim.
- import { useState } from ‘react’; ile useState hook ’unu import ediyoruz.
- export default, fonksiyonun başına yazılmış fakat bu fonksiyonları şu halde de bolca görme şansınız var. Ben şahsen, birazdan göstereceğim üzere kodun sonuna yazıyorum. Sebebini şu an açıklamaya gerek yok. Onunla bir işimiz yok.
Burada göreceğiniz üzere, işlevimiz bir fonksiyonun içerisine entegre edilmiş ve onClick’ten sonra handleClick fonksiyonunun çalışması sağlanıyor.
Bu fonksiyon çalıştığında setState devreye giriyor ve anlık olarak count ’ın değerini 1 arttırıyor.
Bu, arrow function yani ( ) => {} şeklinde yazmaktan mantıklı. En azından şu an.
Gelelim useReducer’a
useReducer Nedir?
Şimdi…
useReducer , React ’ın en önemli hooklarından biridir. Componentlerin State’lerini kontrol etmemize yarar.
Çok popüler bir soruya da cevap verelim ?
Ne zaman useState , ne zaman useReducer kullanacağız?
Öncelikle bir fonksiyonda birden fazla güncellenmesi gereken state olduğunu düşünelim.
Aşağıya koyduğum kod parçası, burada gözlemleyeceğiniz uygulamanın App.js’i dir. İçerisinde işimize lazım 2 component döndürülecek. Biri Post, diğeri Form componentleri.
Css ile hiçbir işimiz yok. Amacımız güzel görünmesi değil.
Post ile başlayalım.
Buraya kadar tamam mı?
Tamam. Yorum satırlarında açıkladım ama karıştıracaklar olabileceği için şöyle de açıklayayım.
3 tane state oluşturdum. Biri loader, biri error, biri post.
Daha sonra handleFetch adında bir fonksiyon oluşturdum. Bu fonksiyon içerisinde bir veri çekme işlemi gerçekleştireceğim. Ondan önce setLoading’i true yaptım ki, yükleniyor… şeklinde bir bilgi versin ve gereksiz tıklanmalarla oluşturulacak tıkanıklıklar önlensin. Temel mantığı bu. Tabii bu yapıldığı sıra butonun da disabled edilmesi gerekiyordu da neyse o şu an önemli değil.
Aynı şekilde error de false ediliyor.
Daha sonra fetch ile veri çekme işlemini gerçekleştiriyoruz.
Veriyi setPost ile post objemizin içerisine yollamadan önce loader ‘i false yapıyoruz. Ve veri işlemini gerçekleştirmiş oluyoruz.
Şimdi bu koda ufak bir kod parçası daha ekleyelim. NEDEN?
Çünkü; veri çekme işlemini gerçekleştirdikten sonra, eğer veriyi çekeceğimiz url bozulursa hatayı yazdıracak bir mekanizma yok. Bunu farkettiysen süper.
Bu kod .then ’den sonra geliyor.
Ee tamam bu ne güzel çalışıyor. Neden useReducer ‘a geçelim?
İşte şimdi geldik fasülyenin faydalarına…
Aşağıda eklediğim kod ile birlikte bir fonksiyon içerisinde 6 tane setState çalıştırmış oluyoruz. Halbuki daha efektif bir şekilde halledebilirdik.
Hadi Bakalım şimdi yazdığımız kodu useReducer’a nasıl uyarlarız onu görelim…
Sonra, postReducer . js dosyamın içerisine, aşağıda görmüş olacağın INITIAL_STATE ‘i oluşturuyorum. Direkt bileşeni (component) de yazabilirsin ama ben bunu tavsiye ediyorum. Böylece sürekli setState çağırmak zorunda kalmayacağız.
Şimdi reducer fonksiyonumuzu oluşturabiliriz.
Bu basit olarak state ‘imizi güncelleyebilecek ve yeni state versiyonunu döndürebilecek bir fonksiyondur. state, INITIAL_STATE içerisindeki verilerimizi kapsar. Action ‘u ise yakında açıklayacağım.
Örneklendirmek gerekirse;
Tıklamadan önce state ‘imizin loading ‘i false, post ‘u {} boş. error ‘ü ise false. Yani ilk aşamada INITIAL_STATE ‘de belirlediğimiz değerler.
Daha sonra butona tıklandığında,
Burada fetch işleminin başladığını görmekteyiz. Aşağıdaki reducer değerlerimize baktığımızda ise, hala initial state değerlerimizin olduğunu görürüz. Bir aşama sonrasına gittiğimizde ise,
Bu aşamada loading ‘in true ’ya döndüğünü, post ‘un hala değişmediğini çünkü fetch işleminin henüz yeni başladığını, error ‘ün ise false olarak döndüğünü görüyoruz.
Şimdi fetch işleminin başarıyla tamamlanması ve sonrasında yaşananları görelim.
Tamam, muhtemelen şimdi yapılacak şey artık state değerlerinin güncellenmesi olacaktır değil mi?
Amacımız bu mesaj alındı yazısıydı. Şimdi artık seve seve state ‘imizin güncellenmesini sağlayabiliriz.
Sonunda diyorum fakat şaka yapıyorum. Bunlar çok kısa süreler, bizim belki de fark etmeyeceğimiz sürelerden oluşuyor.
State ‘in yeni değerleri
loading: false,
post: {id:1, title:`lorem`, desc:`hello world`},
error: false
olarak değişti.
Eğer Bu İşlemler Yaşanırken Hata Olsaydı Ne Olurdu?
Bu durumda, state hali hazırda şu an fetch sırasındadır. Yani failure yaşanırken aslında fetch işlemi gerçekleşiyor. İstenmeyen durumu .catch sayesinde yakalıyor ve bir sonraki aşama olacak olan;
loading: false,
post: {},
error: true
Gerçekleşiyor..
Tamam. Peki Action Neydi?
Action sayesinde tüm bu bilgi değişimlerinin gönderimini sağlayacağız. Nasıl mı? Hadi detaylı bakalım.
Öncelikle reducerPost fonksiyonumuzu hatırla. Bu fonksiyona (state, action) parametrelerini vermiştik. Şimdi bu fonksiyonu dolduralım.
action.type eğer FETCH_START’a eşitse aşağıdaki değerleri döndür demek. Hepsini tek tek açıklamaya gerek yok. Ayrıca FETCH_START büyük yazılınca sanki JS’nin verdiği bir şeymiş gibi geliyor olabilir ama alakası yok. İstediğini yazabilirsin. Onu nereden alındığını şimdi görelim. Şimdi doğruca Post.jsx ‘imize gidiyoruz.
⚠ ️data:payload değil. payload: data olacak. Yanlış yazmışım. 🤦♂️
Lakin, if else ‘ler ile boğuşmak pek mantıklı değil. Biz bunu Switch Case’e döndürelim.
Evet bu çok daha iyi bir yol oldu. Daha iyisi de var. Her birini her seferinde yazdırmamıza gerek yok. Sağda görmüş olduğun örnekte bunu kullanıyoruz. Bu hali hazırda olan state değerlerini oraya yazdırıyor ve değişiklik yapıldığında sadece değişikliklerin değişmesini sağlıyor. Çok fazla state değişimi gereken uygulamalarda çok yararlı bir şeydir.
Şimdi anlatayım:
- react içerisine useReducer hook ‘umu import ettim.
- reducerPost, INITIAL_STATE ‘i de postReducer.js ‘den import ettim.
- useReducer kullanmak için bazı şeylere ihtiyacımız var. Birincisi; useReducer(reducerPost, INITIAL_STATE) olarak yazıyoruz. Bunlardan ilki zaten bizim reducerPost fonksiyonumuz. Diğeri de ilk değer olarak belirlediğimiz state ’imiz. Daha sonra const [state, dispatch] = diyerek useReducer ‘ımızı eşitliyoruz. Burada state bizim state ’imizi , dispatch ise bir fonksiyonu simgeliyor.
- Dispatch fonksiyonu aşağıda yaşanan değişimleri postReducer.js dosyasına yollamamıza yarıyor. Bu yüzden .then bloklarının içerisinde dispatch ile veri aktarımı sağlıyoruz. Ondan önce bahsettiğim gibi {type:”FETCH_START”} ‘ı olduğu gibi dispatch içerisine taşıdık.
- div içerisindeki arkadaşları da state. diyerek sarıyoruz. Çünkü useReducer için belirlediğimiz state’i burada kullanmamız gerekiyor. useReducer içerisindeki state, postReducer içerisinde reducerPost fonksiyonunun parametresi olan state ve dolayısıyla INITIAL_STATE değerlerimize eşitlenmiş oluyor.
- Ve uygulamamız tamam. Şimdi son ve kompleks bir yapı içerisine dalalım.
Form için useReducer kullanımı nasıl olur?
Hadi gel ikinci, kompleks olan kullanıma göz atalım…
Bu bir form bileşeni.
Şimdi bunun 2 türlü yapılışı var. Birincisi: useState kullanarak nasıl yapılıyor? Bunları anlatmayı isterdim de yazı feci uzun oldu. O yüzden sadece kodların fotoğrafını atıp direkt useReducer’a geçeceğim. İlerde useState ile ilgili bir yazı yazarsam anlatırım.
Sıra useReducer ile kullanımda:
Öncelikle formReducer.js adında bir dosya oluşturuyorum. Bu dosyaya INITAL_STATE ‘imi ekliyorum. Tüm state değerlerimi içerisine ekliyorum. Daha sonra aşağıda görüleceği üzere formReducer adında bir fonksiyon oluşturuyorum. Bu fonksiyonun tahmin edeceğiniz üzere 2 parametresi var. Biri state, diğeri ise action. Fonksiyonumu export ediyorum.
switch case durumlarını da düzgünce yazdım. Birazdan değişiklik yapacağım.
Şimdi… birden fazla input varsa onları nasıl tek bir kod yazarak değiştirebileceğiz onu görelim.
Ayrıca yukarda useState ile yapılan versiyonunu eklemiştim ya, ona ihtiyacımız yok burada. O yüzden o fonksiyonları ve etiketlerin içerisindeki yapıları silip tekrar yazıyorum. Burada ilk olarak handleChange’e ihtiyacım var.
name’leri state’lerine eşit olmalı.
handleChange adında bir fonksiyon oluşturuyorum. Bu fonksiyon (e) parametresini alıyor. Biliyorsun ki e’yi genellikle e.target.value gibi şeyler için kullanıyoruz. Burada hangi harfi aldığının bir önemi yok. a dersen de çalışır.
Fonksiyonumun içerisine dispatch fonksiyonumu entegre ediyorum ve ilk olarak type’ımı giriyorum. Type’ım CHANGE_INPUT’tu. Daha sonra payload’ımı gireceğim. Bunun için de
payload: {name: e.target.name, value: e.target.value} yazmam gerekiyor.
Sebebi, biz JS’de bir input’tan verileri görmek istersek bunu target.value ile yapıyoruz. Burada da name ile value eşleştirmesi yapıldı ki çakışma olmasın.
Bunları state’e kaydetmek için de onChange= {handleChange} olarak input elementlerine kaydetmen gerekli. Bunu unutursan bir değişiklik göremezsin.
Şimdi formReducer’ımıza bakalım:
Açıklayayım mı? tm tm açıklıyorum. Zaten kendi kendime anlatıyorum ya hani 🤣 unutursam ve burayı okumak için açtığımda, açıklamadığımı görürsem kahrolurum 😂
…state, daha önceki state verilerimizi simgeliyor. Bunu söylemiştik. [action.payload.name ise , name: e.target.name ‘den geliyor ], action.payload.value ise (value: e.target.value) ‘yi çekiyor. Böylece name’ine ulaştığımız state değiştiğinde sadece ondaki değişiklikleri yazdırabiliyoruz. Bir taşla 3 kuş.
quantity state’imi alıyorum ve state.quantity + 1' e eşitliyorum. Diğeri de -1 yaparak. Ve tahmin et bunu nasıl yapacağız? Doğru tahmin ettin.👌 onClick vererek.
Sıra geldi, diğer state arkadaşlara.
Tag’leri ayırmak için, öncelikle textarea’ya ulaşmam lazım. Bunun için ilkten tagRef adında bir useRef kullanacağım.
handleTags bizim tag’lerimizi eklememize yarayacak. Bunu yaparken de split kullanacağız çünkü aralarındaki virgüllerden kurtulmamız gerekiyor. Peki verilere nasıl ulaşacağız? Az önce söylediğim gibi, useRef aracılığıyla. Onun için tagRef.current… diye gidiyor.
Daha sonra bu verileri ayırdık ve tek tek aşağıya eklememiz gerekiyor. Bunun için de forEach kullanıyoruz. sonrası zaten bildiğin gibi şimdi de reducer tarafına bakalım.
ADD_TAG = tags arrayimi al, eskileri de kat, action.payload ‘daki kodu okuyarak yenileri ekle demek.
Peki REMOVE_TAG?
tags Array’im var. Bunu filter methoduna sok. Klasik silme işlemini gerçekleştirdiğimiz filter methodu. Seçtiğimiz seçeneğin silinmesini sağlayacak.
Silme için de önce key={tag} veriyorum çünkü; console’da key ile ilgili hata veriyor. Daha sonra onClick yaparak, yine dispatch’imi yazıyorum. payload’ım .map’i baz alarak tag olacak. tags değil.
(key={tag} vermek çok mantıklı bir çözüm değil. Sadece hata almamak için yaptık.)
EVET BU KADAR!
Umarım bir şeyler katmıştır. Buraya kadar okuyan varsa zaten helal olsun 😄
Beni LinkedIn ya da Github’dan takip edebilirsiniz:
Proje kodlarını ekliyorum. LamaDev’e de tekrardan böyle bir içerik hazırladığı için teşekkürler. Kendisini takip etmeyi unutmayın. Görüşürüz. ✌️