PROA

Professional Academy

React Hooks

Agenda

Apa saja yang akan dibahas?

  • Apa itu Hooks
  • Basic Hooks
  • Advanced Hooks
  • QnA

Apa itu Hooks

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).

Apa itu Hooks (ii)

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>
  );
}

Apa itu Hooks (iii)

  • useState, serta fungsi lain yang diawali dengan use, merupakan suatu Hook
  • Memperbolehkan kita untuk menggunakan fungsi inti dari React
  • State adalah salah satu yang bisa digunakan, tapi akan ada yang lain selain State yang bisa digunakan
  • Hook harus didefinisikan dengan cara import
  • Sama sekali tidak perlu menggunakan class !

Cara Penggunaan Hooks

  • Hooks hanya bisa didefinisikan di atas atas Komponen atau di dalam Hooks
  • Hooks tidak bisa dipanggil di dalam kondisional, iterasi, ataupun nested function
  • Hooks hanya bisa dipanggil dari Fungsi React

Hooks Dasar

  • useState
  • useEffect

useState

  • Hook yang memperbolehkan kita mengakses state dari komponen
  • Anggap saja state adalah suatu data "keadaan" dari sebuah komponen sekarang ini
  • Misal pada Applikasi Todos yang memiliki component TodosForm, maka kita memiliki "keadaan" (state) sebuah input untuk memanipulasi input yang ada pada TodosForm dengan nama todo
  • Merupakan suatu ide yang bagus apabila memiliki BANYAK variabel state apabila state tersebut tidak berhubungan
  • Namun, apabila berhubungan (sering ganti state barengan), maka ada baiknya digabungkan menjadi suatu Object / Array
  • State ini sifatnya private terhadap suatu komponennya. Apabila kita membuat 2 komponen di tempat yang berbeda, maka tiap komponen tersebut akan memiliki statenya masing masing (tidak sharing state)

useState - Declare & Use

// 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>
  )
}

useState - Demo

Mari kita coba untuk melihat kode yang sudah dibuat kemarin yah !

Kode tersebut dapat dilihat dengan

Klik di sini

useEffect

  • Hooks Effect ini memperbolehkan kita untuk menggunakan side effect di dalam function component
  • Side effect yang dimaksudkan adalah seperti nyomot data, mendengarkan subscription, dan mengganti DOM secara manual di dalam function component
  • Secara default, useEffect ini akan berjalan ketika pertama kali komponen dirender dan setiap kali state yang dipilih terupdate

useEffect - Demo

Mari 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 - Deep Dive

  useEffect(
    // Function
    () => {
      let textTitle = 'Todos: ' + todos.length;
      console.log(textTitle);
      document.title = textTitle;
    },
    // Dependency List
    [todos]
  );
  • Function
    • Fungsi yang memiliki kode imperatif, yang mungkin ada efek samping (mengubah sesuatu), mis, mengubah DOM, atau mendengarkan event - resize window dan melihat ukuran window, dll
    • Pada Fungsi di atas, kita meminta React untuk mengganti title dari page (Memanipulasi DOM) dan menuliskan console log saja
  • Dependency List
    • Defaultnya, useEffect akan berjalan setiap render terjadi (tiap ada perubahan terhadap state APAPUN).
    • Pada Dependency List di atas, kita meminta React untuk memanggil fungsi dalam useEffect HANYA ketika state todos berubah.

useEffect - Kapan Digunakan?

Jadi sekarang pertanyaannya berdasarkan demo sekilas tadi, kapan sih penggunaan useEffect ?

  • Ketika ingin membaca dan menyimpan data di media penyimpanan lokal
  • Ketika ingin mengambil data dari API / Database
  • Mendengarkan (Subscribe) terhadap sesuatu, e.g. websocket, custom event, dsb
  • Mengupdate DOM secara manual
  • Menunggu suatu state untuk berubah sebelum kita menjalankan sesuatu.
    • Contoh: Kita memiliki sebuah halaman untuk edit, kemudian kita harus me-nembak API untuk update data di server (remote data), dan setelah itu kita harus mengupdate state (local data)

useEffect - Notes

Beberapa notes ketika menggunakan useEffect

  • Ketika kita hanya ingin menggunakan useEffect satu kali saja, maka, pada dependency list, kita PERLU memberikan array kosong
  • Apabila kita tidak memberikan array kosong, maka, SETIAP semua state ada yang berubah, useEffect akan berjalan (bisa jadi infinite loop / infinite fetch data !)
  • Umumnya, kita hanya akan fetch data satu kali untuk halaman yang ada, KECUALI ada perubahan yang harus membuat kita fetch ulang
  • Seperti halnya dengan useState yang bisa dibuat banyak, useEffect di dalam sebuah component pun bisa dibuat banyak. Sehingga kita bisa memisahkan logic sesuai dengan kebutuhan effect

useEffect - Use Case Dunia Nyata

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);
    };
  });
  ...
}

useEffect - Demo

Yuk tanpa lama lama lagi, kita coba memasukkan useEffect di dalam kode ToDo yang dibuat yah !

Kode dapat dilihat

Klik di sini

Hooks Lanjutan

  • Context & setContext
  • useReducer

Context

Ketika 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

  • Kita mau sharing props antar komponen, namun ternyata cabangnya banyak sekali !
  • Mungkin kita butuh passing propsnya seperti ini: Komponen OrangTua -> Anak -> Cucu -> Cicit dst
  • Padahal kita sekarang butuh hanya dari si OrangTua -> Cicit saja
  • Kondisi ini disebut dengan props drilling

Context

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

Context (ii)

Misalkan kita menginginkan beberapa Header yang ada di dalam section yang sama akan selalu memiliki ukuran yang sama

Bila saja kita bisa mempassing 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>

Context (iii)

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:

  • Membuat context
  • Menyediakan context dari komponen yang menyediakan data
  • Menggunakan context ke komponen yang membutuhkan data

context bisa membuat OrangTua, bahkan yang jauh banget pun !, menyediakan data untuk seluruh komponen yang ada di dalamnya (secara "teleport") tadi

createContext

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);

Context - Define Parent to Child

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>
  )
}

Context - Define Parent to Child (ii)

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>
  )
}

Context - Digunakan di Child

// 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);

  ...
}

Context - Digunakan di Child (ii)

// 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);

  ...
}

Contoh Full Code

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 sini

Lihat pada bagian App.js - NestedComponengWithContext

Ada cara lebih pendeknya loh !

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>

Loh kok bisa?

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 sini

Lihat pada bagian App.js - NestedComponengWithContextPart2

Context dan useContext - WARNING

Beberapa saran penggunaan Context dan useContext :

  • Awali dengan penggunaan props
    • Sebenarnya umum sekali untuk melempar beberapa props melewati beberapa Komponen, karena lebih jelas terlihat komponen mana yang membutuhkan props tersebut. (Jadi awali dengan props, bila terlalu memberatkan, bisa menggunakan context)
  • Ekstrak komponen dan melempar JSX sebagai anakan ke komponen tersebut
    • Sebenarnya apabila kita melempar beberapa data lewat Komponen "tengah" yang tidak menggunakan data tersebut (cuma lewat saja), umumnya ini artinya kita masih lupa untuk mengekstrak beberapa komponen di tengah tersebut (TL;DR: Buatlah sebuah Higher Order Component-nya !)

Context dan useContext - Kapan Digunakan?

  • Pembuatan Theme (e.g. Dark Mode, Custom Theme)
  • Untuk melihat User yang sedang log-in sekarang ini
  • Routing (untuk menginfokan: eh, sekarang lagi di rute yang mana yah?)
    • Untuk Routing akan ada materi tersendiri nanti
  • Manajemen State (data)
    • Seiring berkembangnya aplikasi yang dibuat, sangat umum untuk menggunakan reducer yang dikombinasikan dengan context untuk manajemen state yang kompleks dan dilempar ke komponen yang jauh urutannya tanpa ribet.

useReducer

Masih ingat dengan .reduce di dalam Array?

React juga punya reducernya loh !

  • React reducer mengambil ide yang sama dengan array dan mengkonversi ke ala React: Mengambil state dan action, kemudian akan mengembalikan state selanjutnya.
  • Sehingga terjadi akumulasi action dalam rentang waktu tertentu ke terhadap state
  • Merupakan alternatif dari useState. Menerima reducer dengan tipe (state, action) => stateBaru, dan mengembalikan state sekarang yang dikombinasikan dengan method dispatch
  • useReducer akan lebih disarankan daripada useState APABILA KITA SUDAH MEMILIKI LOGIC STATE YANG KOMPLEKS yang memiliki sub-nilai (nilai dalam nilai) atau ketika state selanjutnya bergantung dari state sebelumnya

useReducer

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:

  • Memindahkan setting state ke dispatching action
  • Menuliskan fungsi reducer
  • Menggunakan reducer di dalam komponen

reducer - Cara Deklarasi

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");
  }
}

useReducer - Cara Pakai

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
    })
  }
}

useReducer - Full Code

Mari kita coba mengubah Container ToDo.jsx menjadi versi reducer

Di sini

Komparasi useState vs useReducer

  • Ukuran kode - useState kode yang tertulis akan lebih kecil di awal, namun untuk useReducer akan lebih simple dan lebih mudah untuk dipahami apabila logic untuk memodifikasi statenya mirip
  • Kemudahan membaca kode - useState akan lebih mudah dibaca apabila kodenya masih simple. useReducer akan memisahkan logic untuk update dan event handlernya
  • Testing - karena reducer adalah murni fungsi saja, dapat dilakukan pengetesan secara lebih terisolasi ketimbang State yang menempel pada komponen yang ada.

Tapi sebenarnya kita akan kembali pada preferensi masing masing. Sebenarnya bisa kita menggunakan keduanya secara equivalent. Bisa dicampur campur kok !

React itu "mudah" bukan?

Bukan !

Hayo jangan menyerah !

QnA

Referensi

  • https://beta.reactjs.org/learn/sharing-state-between-components
  • https://reactjs.org/docs/context.html
  • https://beta.reactjs.org/learn/passing-data-deeply-with-context
  • https://beta.reactjs.org/learn/extracting-state-logic-into-a-reducer
withered-flowers (2022)