Apa saja yang akan dibahas?
JASMERAH
Dulu, React itu dedefinisikan dalam class, namun sejak React 16.8, React itu menggunakan function
Hooks adalah fungsi yang disediakan oleh React yang membuat functional component dalam React bisa mengakses fitur fitur React (Sebelumnya hanya dapat digunakan di class component saja).
The class ways
import React from 'react';
export default class HelloComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
this.handleCounterChange = this.handleCounterChange.bind(this);
}
handleCounterChange(e) {
this.setState({
counter: this.state.counter + 1,
});
}
render() {
return (
<div>
<p>Counter: {this.state.counter}</p>
<button onClick={this.handleCounterChange}>Increment</button>
</div>
);
}
}
The function ways
import React, { useState } from 'react';
export default function HelloComponent() {
const [counter, setCounter] = useState(0);
function handleCounterChange(e) {
setCounter(counter + 1);
}
return (
<div>
<p>Counter: {counter}</p>
<button onClick={handleCounterChange}>Increment</button>
</div>
);
}
// ceritanya ini Component
const Todos = () => {
/*
declare useState dengan nama statenya adalah "todos"
todos adalah state
| setTodos adalah fungsi untuk mengubah state todo
| | useState adalah Hooks nya
v v v [] adalah initial value untuk todo */
const [todos, setTodos] = useState([]);
// Cara menggunakan fungsi pengubah state (setTodos)
// anggap adalah sebuah array yang immutable (tidak dapat diassign)
// sehingga cara nambahin array dengan buat array baru, kemudian
// menambahkan data terakhir dengan param todo yang dimasukkan
function eventHandler = (todo) => {
setTodos([...todos, todo]);
}
return (
<div>
{/* Cara menggunakan state (todos)
misalnya ini adalah komponen yang bisa menerima props
todosProps, yang menerima state todos */}
<TodosComponent todosProps={todos}>
</div>
)
}
Mari kita coba untuk melihat kode yang sudah dibuat kemarin yah !
Kode tersebut dapat dilihat dengan
Klik di siniMari kita coba dari sisi penggunaan useEffect untuk mengganti DOM secara manual
Mari kita ubah file containers ToDo.jsx
import React, { useEffect, useState } from 'react';
function ToDo() {
...
// useEffect menerima DUA paramater
// parameter-1 adalah fungsi yang akan dijalankan
// parameter-2 adalah list dependensi terhadap useEffect
// bila kosong, untuk tiap state yang berubah,
// useEffect akan DIJALANKAN terus !
useEffect(
() => {
let textTitle = 'Todos: ' + todos.length;
console.log(textTitle);
document.title = textTitle;
},
// Di sini kita menyatakan bahwa useEffect akan selalu dijalankan lagi
// apabila state todos berubah
[todos]
);
return (
...
)
}
useEffect(
// Function
() => {
let textTitle = 'Todos: ' + todos.length;
console.log(textTitle);
document.title = textTitle;
},
// Dependency List
[todos]
);
Jadi sekarang pertanyaannya berdasarkan demo sekilas tadi, kapan sih penggunaan useEffect ?
Beberapa notes ketika menggunakan useEffect
Sneak peek real use case dalam penggunaan useEffect
const FriendStatusWithCounter = (props) => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Sudah klik: ${count} kali`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
const handleStatusChange = (status) => {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeToFriendStatus(props.friend.id, handleStatusChange);
};
});
...
}
Yuk tanpa lama lama lagi, kita coba memasukkan useEffect di dalam kode ToDo yang dibuat yah !
Kode dapat dilihat
Klik di siniKetika kita menggunakan props itu, rasanya baik baik saja yah?
Bisa mindahin atau angkat state yang dibutuhkan ke level lebih tinggi?
Bisa passing dari data dari Komponen OrangTua ke Komponen Anakannya?
Semua akan baik baik saja sampai dengan...
Terjadi suatu kondisi seperti ini
Bagaimana bila seandainya kita memiliki suatu cara untuk men-teleport data dari props OrangTua secara langsung ke Cicit secara instant?
Solusinya adalah kita menggunakan Context
Misalkan kita menginginkan beberapa Header yang ada di dalam section yang sama akan selalu memiliki ukuran yang sama
Bila saja kita bisa mem-passing ukuran dari section ke komponennya ...
<Section>
<Section>
<Heading level={1}>Title</Heading>
<Section>
<Heading level={2}>Heading</Heading>
<Heading level={2}>Heading</Heading>
<Heading level={2}>Heading</Heading>
<Section>
<Heading level={3}>Sub-heading</Heading>
<Heading level={3}>Sub-heading</Heading>
<Heading level={3}>Sub-heading</Heading>
<Section>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
</Section>
-----
<Section>
<Section level={1}>
<Heading>Title</Heading>
<Section level={2}>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Section level={3}>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Section level={4}>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
</Section>
Loh... Bagaimana caranya Heading "meminta" data dari tempat lain yang secara hierarki ada di atas nya dia banget?
Di sinilah Context bekerja !
Langkahnya adalah sebagai berikut:
context bisa membuat OrangTua, bahkan yang jauh banget pun !, menyediakan data untuk seluruh komponen yang ada di dalamnya (secara "teleport") tadi
Cara untuk deklarasi context
import React, {createContext} from 'react';
// Cara deklarasi context
// Biasanya ini akan ditaruh di file yang berbeda dari component
// sehingga jangan lupa di export
export const NamaContext = createContext("default-value-si-context");
import React, {createContext} from 'react';
// Untuk menyelesaikan masalah Level sebelumnya
export const LevelContext = createContext(1);
Cara untuk mendefinisikan context di ComponentOrangTua
import React from 'react';
// Ingat di sini karena contextnya bisa banyak
// jadi importnya dengan cara destructuring
import { NamaContext } from "./path/ke/context.js";
const ComponentParent = ({valYangDilempar}) => {
return (
<TagOrangTua>
{/* di sini kita menggunakan NamaContext.Provider */}
{/* Cara baca: Apabila Component di dalam TagOrangTua */}
{/* meminta NamaContext, berikan valYangDilempar */}
<NamaContext.Provider value={valYangDilempar}>
{/* sehingga ComponentAnak bisa akses NamaContext */}
<ComponentAnak />
</NamaContext.Provider>
</TagOrangTua>
)
}
Cara untuk mendefinisikan context berdasarkan contoh
import React from 'react';
// Ingat di sini karena contextnya bisa banyak
// jadi importnya dengan cara destructuring
import { LevelContext } from "./path/ke/LevelContext.js";
export default function Section({level, children}) {
return (
<section className="section">
{/* Di sini komponen Section akan menyediakan */}
{/* LevelContext untuk digunakan oleh komponen Anak-anaknya*/}
{/* Children component */}
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
</section>
)
}
// Import useContext
import React, { useContext } from 'react';
// Import context yang ingin digunakan
import NamaContext from "./path/ke/context.js";
export default function ChildComponent() {
// Di sini ChildComponent meminta value dari context
// yang didefinisikan oleh ParentComponent
const valContext = useContext(NamaContext);
...
}
// Import useContext
import React, { useContext } from 'react';
// Import context yang ingin digunakan
import LevelContext from "./path/ke/LevelContext.js";
export default function Heading({ children }) {
// Di sini Heading meminta value dari LevelContext
// yang didefinisikan oleh ParentComponent-nya (Section)
const level = useContext(LevelContext);
...
}
Bingung yah kl tidak ada kodenya?
Yuk kita coba demokan dengan menggunakan dengan melanjutkan ToDo kita tadi dengan menambahkan Section dan Heading di dalamnya
DANGER: kode di dalam sini cukup panjang dan terbagi jadi beberapa bagian, jadi mohon nonton recordingnya bila kurang mengerti yah !
Klik di siniLihat pada bagian App.js - NestedComponentWithContext
Bagaimana jika ada cara lebih mudahnya lagi untuk kasus kita yang tadi? (untuk deklarasi level)
Kita bahkan tidak perlu deklarasiin !
<Section>
<Section>
<Heading>Title</Heading>
<Section>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Heading>Heading</Heading>
<Section>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Heading>Sub-heading</Heading>
<Section>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
</Section>
</Section>
</Section>
</Section>
Context memperbolehkan kita membaca informasi dari Komponen yang ada di atasnya, sehingga setiap Section bisa membaca level dari Section di atasnya ! (dan secara langsung, bisa memberikan level + 1 secara otomatis)
Klik di siniLihat pada bagian App.js - NestedComponentWithContextPart2
Beberapa saran penggunaan Context dan useContext :
Masih ingat dengan .reduce di dalam Array?
React juga punya reducernya loh !
Seiring dengan berkembangnya komponen, pastinya statenya akan berkembang banyak juga. Untuk mengurangi kompleksitas dan membuat logic di satu tempat-yang-mudah-diakses, kita bisa memindahkan logic state ke dalam SEBUAH FUNGSI di luar komponen, yang disebut sebagai reducer
Kita bisa melakukan migrasi dari useState ke useReducer dengan 3 langkah berikut:
Cara mendeklarasi reducer
// Biasanya reducer ada di file yang lain (mirip context)
// jangan lupa diexport
// di sini fungsinya menerima 2 parameter
// data (mirip state) dan action
export default function NamaReducer(data, action) {
// biasanya di reducer, di dalam action
// ada sebuah property yang dijadikan kondisional
switch(action.conditionalProperty) {
case 'val1':
// lakukan logicnya
// umumnya akan mengembalikan data yang baru
...
// biasanya di dalam reducer akan ada mereturn data baru itu
return dataBaruYangTermodifikasi
case 'val2':
...
return dataBaruYangTermodifikasi
// selalu ada default agar ada error bisa tertangkap
default: throw Error("message ketika error");
}
}
Cara pakai reducer
// import useReducer
import React, { useReducer } from 'react';
import NamaReducer from "./path/ke/fungsi/reducer.js";
// Data awalnya sekarang ditaruh di luar component, karena ini bukan state
const dataAwal = [
...
];
const ComponentPenggunaReducer = () => {
// declare penggunaan reducer
// menerima 2 parameter
// Parameter-1: Fungsi reducer yang digunakan
// Parameter-2: Data awal yang akan dijadikan patokan
// (anggap sebagai state-nya reducer)
// Mengembalikan dataAkumulatifnya dan dispatch (callback)
const [dataAccumulator, dispatch] = useReducer(NamaReducer, dataAwal);
function pemanggilReducer() {
// untuk memanggil reducer, gunakan dispatch(action)
dispatch({
// kirim action ke reducer, umumnya banyak data yang dikirimkan
// jadi bentuk dalam object
})
}
}
Mari kita coba mengubah Container ToDo.jsx menjadi versi reducer
Di siniTapi sebenarnya kita akan kembali pada preferensi masing masing. Sebenarnya bisa kita menggunakan keduanya secara equivalent. Bisa dicampur campur kok !
Bukan !
Hayo jangan menyerah !