Image by Ian Schneider
Series membuat react adalah sebuah series dimana kita akan belajar memahami react dengan membuat react. Ini adalah part 2 dari series ini, di part sebelumnya saya membuat tentang bagaimana react membangun virtual dom dan merender JSX. Di part ini saya ingin melanjutkan membuat react dengan membuat salah satu react hook yang paling sering digunakan yaitu useState.
Rules pada react hooks
Ada beberapa aturan terlarang terkait hooks yang tidak boleh dilanggar karena dapat berakibat fatal
- Jangan taruh hook dalam loop
- Jangan taruh hook pada conidtional statement
- Jangan taruh hook pada event handler
- Jangan taruh hook pada class component
- Jangan taruh hook dalam fungsi useMemo, useReducer, dan useEffect
untuk lebih lengkap mengenai beberapa aturan tersebut kalian bisa buka ke dokumentasi react langsung. Pada saat mengerti bagaimana hook bekerja, aturan-aturan diatas seharusnya dapat dipahami.
useState
Fungsi useState biasanya digunakan untuk membuat reactive variable pada react. useState akan mengembalikan array yang berisi nilai variable itu sendiri dan juga fungsi set yang digunakan untuk mengubah nilai state atau value variable. Efek samping dari mengubah nilai state pada useState adalah re-render pada komponen.
const React = {
useState: function (initialValue) {
const value = initialValue;
const setValue = (newValue) => {
value = newValue;
};
return [value, setValue];
}
}
Kode diatas adalah struktur dasar dalam membangun fungsi useState dimana fungsi menerima parameter initial value lalu mengembalikan kembali value tersebut dan juga fungsi untuk merubah value tersebut. Struktur tersebut cukup sederhana tapi tentu fungsi useState lebih dari itu.
Yang pertama, kita tau kalo useState bisa digunakan berkali-kali untuk mendefinisikan state atau variable yang berbeda-beda. Maka kita butuh sebuah tempat dimana kita dapat menyimpan semua value dari semua useState yang didefinisikan.
const React = {
stateIndex = -1,
hooks = [],
// Rest of the code
}
Disini kita bisa buat sebuah array dimana kita dapat menyimpan semua variable useState. Selain itu kita juga mendefiniskan sebuah stateIndex, fungsi stateIndex ini adalah untuk mengetahui state mana yang akan kita berikan ketika fungsi useState dipanggil.
const React = {
stateIndex = -1,
hooks = [],
useState: function (initialValue) {
this.stateIndex++;
if(!this.hooks[this.stateIndex]) {
this.hooks[this.stateIndex] = initialValue;
}
const value = this.hooks[this.stateIndex];
const setValue = (newValue) => {
this.hooks[this.stateIndex] = newValue;
};
return [value, setValue];
}
}
Dengan adanya global variable dan juga stateIndex, kita dapat mendefiniskan useState berulang kali dan akan memberikan value yang berbeda-beda.
// Contoh penggunaan
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Heru");
return (
<div>
<p>{ count }</p>
<p>{ name }</p>
</div>
)
}
Jika kita menjalankan kode pada React kita maka kita akan mendapatkan text “0” dan juga “Heru”. Pada tahap ini fungsi useState kita sudah dapat menyimpan variable dan merendernya ke virtual dom. Tapi saat ini fungsi setState kita belum berjalan sesuai ayng diharapkan.
Hal yang perlu diperhatikan ketika memanggil sebuah setState function adalah re-render. Kita perlu merender ulang komponent kita saat terjadi sebuah perubahan state. Hal ini cukup sederhana, kita hanya perlu memanggil ulang fungsi render yang sudah kita buat sebelumnya namun dengan sedikit perubahan. Kita perlu menyimpan rootDom and juga rootVdom agar bisa melakukan rerendering.
const React = {
rootDom: null,
rootVdom: null,
render: (vdom, parentDom) {
if (!this.rootVdom && !this.rootDom) {
this.rootDom = parentDom;
this.rootVdom = vdom;
}
// ...
// Rest of the code
}
}
Dengan menginisialisasi rootDom dan rootVdom kita dapat melakukan rerender dengan cara menghapus isi dari rootDom dan mengisinya kembali dengan rootVdom.
const React = {
stateIndex = -1,
hooks = [],
useState: function (initialValue) {
this.stateIndex++;
if(!this.hooks[this.stateIndex]) {
this.hooks[this.stateIndex] = initialValue;
}
let currentIndex = this.stateIndex;
const value = this.hooks[this.stateIndex];
const setValue = (newValue) => {
this.hooks[currentIndex] = newValue;
this.stateIndex = -1;
this.rootDom.innerHTML = "";
this.render(this.rootVdom, this.rootDom);
};
return [value, setValue];
}
}
Pada kode diatas saya melakukan render ulang dengan menghapus isi dari rootDom terlebih dahulu, perlu dicatat bahwa cara tersebut adalah cara yang sangat tidak efisien. React yang asli melakukan teknik yang lebih advance dalam melakukan rerendering.
Hal yang juga saya tambahkan adalah variable currentIndex, variable ini berfungsi untuk menyimpan stateIndex sementara dalam fungsi useState dikarenakan fungsi useState yang dapat dipanggil berulang kali untuk mendefiniskan state yang berbeda maka kita dapat menggunakan stateIndex dalam fungsi setState karena akan mengarah ke state yang berbeda. Selain itu kita juga mereset stateIndex karena kita akan melakukan rerender yang artinya fungsi setState dalam komponen akan dipanggil kembali.
// Contoh penggunaan
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Heru");
function addCount() {
setCount(count++);
}
function changeName() {
let newName = "Budi";
setName(newName);
}
return (
<div>
<p>{ count }</p>
<p>{ name }</p>
<button onClick={addCount}>Add count</button>
<button onClick={changeName}>Change name</button>
</div>
)
}
Kembali memahami rules
Sebelumnya kita sudah memahami aturan-aturan dalam menulis react hook dan setelah memahami bagaimana useState seharusnya kita sudah dapat sedikit gambaran kenapa aturan-aturan tersebut dibuat. Dikarenakan behaviour yang dibuat hook itu sendiri dipanggil pada setiap render dan akses yang menggunakan incremental index, maka tentu saja kita dapat mengacaukan flow rendering pada react.
const random_number = Math.random();
if(random_number > 0.5) {
const [count, setCount] = useState(0);
}
Kode diatas dilarang dalam penggunaan useState karena ditaruh pada sebuah conditional. Kode diatas akan menimbulkan bug pada kode react kita karena akan mengacaukan flow stateIndex. Behaviour random pada conditional statement bisa mengakibatkan stateIndex memiliki panjang yang berbeda ketika dilakukan re-rendering.