Escape Hatches

Advanced

Beberapa komponen-komponen Anda mungkin membutuhkan kendali dan sinkronisasi dengan sistem di luar React. Misalkan, Anda mungkin ingin memfokuskan sebuah input dengan menggunakan API peramban, memutar dan menjeda sebuah pemutar video yang diimplementasi tanpa React, atau menghubungkan dan mendengarkan pesan-pesan dari sebuah remote server. Pada bab ini, Anda akan mempelajari jalan keluar (escape hatches) yang membiarkan Anda “melangkah keluar” dari aturan React dan menghubungkannya ke sistem luar. Sebagian besar logika aplikasi dan aliran data Anda sebaiknya tidak bergantung pada fitur-fitur ini.

Mereferensikan nilai menggunakan refs

Ketika Anda ingin sebuah komponen “mengingat” beberapa informasi, tapi Anda tidak ingin informasi tersebut memicu render baru, Anda dapat menggunakan ref:

const ref = useRef(0);

Sama seperti state, refs disimpan oleh React diantara pe-render-an ulang. Namun, mengatur state menyebabkan komponen di-render ulang. Mengganti sebuah ref tidak menyebabkan itu! Anda dapat mengakses nilai saat ini dari ref tersebut melalui properti ref.current.

import { useRef } from 'react';

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
    alert('Anda mengeklik ' + ref.current + ' kali!');
  }

  return (
    <button onClick={handleClick}>
      Klik saya!
    </button>
  );
}

Ref seperti sebuah kantong rahasia dari komponen Anda yang tidak dilacak oleh React. Misalkan, Anda dapat menggunakan refs untuk menyimpan timeout IDs, elemen-elemen DOM, dan objek lainnya yang tidak memengaruhi hasil render sebuah komponen.

Siap mempelajari topik ini?

Baca Mereferensikan Nilai menggunakan Refs untuk mempelajari bagaimana menggunakan refs untuk mengingat informasi.

Baca Lebih Lanjut

Manipulasi DOM dengan Refs

React secara otomatis memperbarui DOM agar sesuai dengan keluaran render, sehingga komponen Anda tidak perlu sering memanipulasinya. Namun, terkadang Anda mungkin perlu mengakses elemen DOM yang dikelola oleh React—misalnya, memberikan fokus pada sebuah simpul (node), menggulir ke sana, atau mengukur ukuran dan posisinya. Tidak ada cara bawaan untuk melakukan hal-hal tersebut di React, sehingga Anda memerlukan ref ke simpul DOM. Sebagai contoh, mengklik tombol akan memfokuskan input menggunakan sebuah ref:

import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Memfokuskan Input
      </button>
    </>
  );
}

Siap mempelajari topik ini?

Baca Manipulasi DOM dengan Refs untuk mempelajari bagaimana cara mengakses elemen DOM yang dikelola oleh React.

Baca Lebih Lanjut

Sinkronisasi dengan Effect

Beberapa komponen perlu melakukan sinkronisasi sistem eksternal. Misalkan, Anda mungkin ingin mengontrol komponen di luar React berdasarkan state React, mengatur koneksi server, atau mengirim log analitik ketika sebuah komponen muncul di layar. Tidak seperti event handlers, yang memungkinkan Anda menangani events tertentu, Effects memungkinkan Anda menjalankan beberapa kode setelah render. Gunakan Effects ini untuk melakukan sinkronisasi dengan sistem di luar React.

Tekan tombol Play/Pause beberapa kali dan lihat bagaimana pemutar video tetep disinkronkan dengan nilai prop isPlaying:

import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);

  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

  return <video ref={ref} src={src} loop playsInline />;
}

export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

Banyak Effects juga melakukan “pembersihan” setelah mereka selesai. Misalkan, sebuah Effect yang mengatur koneksi ke chat server harus mengembalikan fungsi pembersih (cleanup function) yang memberi tahu React bagaimana cara memutuskan koneksi komponen Anda dari server tersebut:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>Welcome to the chat!</h1>;
}

Di mode pengembangan (development), React akan segera menjalankan dan membersihkan Effect Anda dengan satu kali tambahan. Inilah mengapa Anda melihat "✅ Connecting..." tercetak dua kali. Ini memastikan Anda tidak lupa untuk mengimplementasikan fungsi pembersih.

Siap mempelajari topik ini?

Baca Sinkronisasi dengan Effect untuk mempelajari bagaimana menyinkronkan komponen dengan sistem eksternal.

Baca Lebih Lanjut

Anda mungkin tidak membutuhkan Effect

Effects adalah jalan keluar dari paradigma React. Mereka memungkinkan Anda “keluar” dari React dan menyinkronkan komponen Anda dengan sistem eksternal. Jika tidak ada sistem eksternal yang terlibat (misalkan, jika Anda ingin memperbarui state komponen dengan beberapa props atau perubahan state), Anda seharusnya tidak perlu menggunakan Effect. Hilangkan Effects yang tidak perlu akan membuat kode Anda lebih mudah untuk diikuti, lebih cepat untuk dijalankan, dan lebih sedikit berpotensi galat.

Ada dua kasus umum di mana Anda tidak memerlukan Effects:

  • Anda tidak perlu menggunakan Effects untuk mengubah data saat pe-render-an.
  • Anda tidak perlu menggunakan Effects untuk menangani events pengguna.

Sebagai contoh, Anda tidak perlu menggunakan Effect untuk menyesuaikan beberapa state berdasarkan state lainnya:

function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');

// 🔴 Avoid: redundant state and unnecessary Effect
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}

Sebaliknya, lakukan perhitungan sebanyak mungkin saat render:

function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Good: Melakukan perhitungan selama render
const fullName = firstName + ' ' + lastName;
// ...
}

Namun, anda perlu menggunakan Effects untuk menyinkronkannya dengan sistem eksternal.

Siap mempelajari topik ini?

Baca Anda mungkin tidak membutuhkan Effect untuk memplejari bagaimana menghilangkan Effects yang tidak perlu.

Baca Lebih Lanjut

Siklus hidup effects yang reaktif

Effects mempunyai siklus hidup yang berbeda dari komponen. Komponen dapat mount, memperbarui (*update), or unmount. Sebuah Effect hanya dapat melakukan dua hal: memulai menyinkronkan sesuatu, dan kemudian berhenti menyinkronkannya. Siklus ini dapat terjadi berkali-kali jika Effect anda bergantung pada props dan state yang berubah setiap saat.

Effect ini bergantung pada nilai prop roomId. Props adalah nilai yang reaktif, yang artinya mereka dapat berubah saat pe-render-an ulang. Perhatikan bahwa Effect menyinkronkan ulang (dan menghubungkan kembali ke server) jika roomId berubah:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Welcome to the {roomId} room!</h1>;
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

React menyediakan aturan linter untuk memeriksa apakah Anda telah menetapkan dependensi Effect dengan benar. Jika anda lupa untuk menyantumkan roomId dalam daftar dependensi dalam contoh di atas, linter akan menemukan bug secara otomatis.

Siap mempelajari topik ini?

Baca Siklus hidup effects yang reaktif untuk mempelajari bagaimana siklus hidup Effect berbeda dari komponen.

Baca Lebih Lanjut

Memisahkan events dari Effects

Dalam Pengembangan

Bagian ini mendeskripsikan sebuah eksperimen API yang belum dirilis di versi stabil React.

Event handlers hanya berjalan ulang ketika Anda melakukan interaksi yang sama lagi. Tidak seperti event handlers, Effects menyinkronkan ulang jika nilai apapun yang mereka baca, seperti props atau state, berbeda dari saat render terakhir. Kadang, Anda ingin campuran kedua perilaku tersebut: sebuah Effect yang berjalan ulang sebagai respon terhadap beberapa nilai tetapi tidak pada nilai lainnya.

Semua kode di dalam Effects adalah reactive. Effects tersebut akan berjalan lagi jika beberapa nilai reactive yang dibacanya telah berubah karena render ulang. Misalkan, Effect ini akan menghubungkan kembali ke chat jika roomId atau theme telah berubah:

import { useState, useEffect } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Connected!', theme);
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, theme]);

  return <h1>Welcome to the {roomId} room!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Use dark theme
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

Ini tidak ideal. Anda ingin menghubungkan kembali ke chat jika hanya roomId yang berubah. Mengganti theme seharusnya tidak menghubungkan kembali ke chat! Pindahkan pembacaan kode theme dari Effect anda ke dalam Effect Event:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification('Connected!', theme);
  });

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      onConnected();
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Welcome to the {roomId} room!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Use dark theme
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

Kode di dalam Effect Events tidak bersifat reactive, sehingga mengubah theme tidak lagi membuat Effect Anda terhubung kembali.

Siap mempelajari topik ini?

Baca Memisahkan Events dari Effects untuk mempelajari bagaimana mencegah beberapa nilai dari memicu ulang Effects.

Baca Lebih Lanjut

Menghapus Effect dependencies

Ketika Anda menulis sebuah Effect, linter akan memverifikasi bahwa Anda telah menyertakan setiap nilai reactive (seperti props dan state) yang Effect baca di daftar Effect dependencies Anda. Ini memastikan bahwa Effect Anda tetap selaras dengan props dan state terbaru dari komponen Anda. Dependencies yang tidak perlu dapat menyebabkan Effect Anda berjalan terlalu sering, atau bahkan membuat perulangan tak terhingga. Cara Anda menghapusnya tergantung pada kasusnya.

Sebagai contoh, Effect ini bergantung pada objek options yang dibuat ulang setiap kali Anda mengedit input:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  const options = {
    serverUrl: serverUrl,
    roomId: roomId
  };

  useEffect(() => {
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [options]);

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Anda tidak ingin chat terhubung kembali setiap kali Anda memulai mengetik sebuah pesan di dalam chat. Untuk memperbaiki masalah ini, pindahkan pembuatan objek options ke dalam Effect sehingga Effect hanya bergantung pada string roomId:

import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return (
    <>
      <h1>Welcome to the {roomId} room!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <hr />
      <ChatRoom roomId={roomId} />
    </>
  );
}

Perhatikan bahwa Anda tidak memulai dengan mengedit daftar dependency untuk menghapus options dependency. Itu akan salah. Sebaliknya, Anda mengubah kode sekitarnya sehingga dependency-nya menjadi tidak perlu. Anggaplah daftar dependency sebagai daftar semua nilai reactive yang digunakan oleh kode Effect Anda. Anda tidak memilih dengan sengaja apa yang harus dimasukan dalam daftar. Daftar tersebut menjelaskan kode Anda. Untuk mengubah daftar dependency, ubahlah kode.

Siap mempelajari topik ini?

Baca Menghapus Effect Dependencies untuk mempelajari bagaimana membuat Effect Anda berjalan ulang lebih jarang.

Baca Lebih Lanjut

Menggunakan kembali logika dengan Hooks kustom

Aplikasi React dibangun dengan Hooks bawaan seperti useState, useContext, dan useEffect. Kadang, Anda akan berharap ada Hook untuk tujuan yang lebih spesifik: Misalkan, mengambil data, melacak apakah pengguna sedang online, atau menghubungkan ke ruang chat. Untuk melakukannya, anda dapat membuat Hooks sendiri untuk kebutuhan aplikasi Anda.

Pada contoh ini, hook kustom usePointerPosition melacak posisi kursor, sementara hook kustom useDelayedValue mengembalikan nilai yang “tertinggal” dari nilai yang Anda lewatkan selama beberapa milidetik. Geser kursor di atas area pratinjau sandbox untuk melihat jejak titik yang bergerak mengikuti kursor:

import { usePointerPosition } from './usePointerPosition.js';
import { useDelayedValue } from './useDelayedValue.js';

export default function Canvas() {
  const pos1 = usePointerPosition();
  const pos2 = useDelayedValue(pos1, 100);
  const pos3 = useDelayedValue(pos2, 200);
  const pos4 = useDelayedValue(pos3, 100);
  const pos5 = useDelayedValue(pos4, 50);
  return (
    <>
      <Dot position={pos1} opacity={1} />
      <Dot position={pos2} opacity={0.8} />
      <Dot position={pos3} opacity={0.6} />
      <Dot position={pos4} opacity={0.4} />
      <Dot position={pos5} opacity={0.2} />
    </>
  );
}

function Dot({ position, opacity }) {
  return (
    <div style={{
      position: 'absolute',
      backgroundColor: 'pink',
      borderRadius: '50%',
      opacity,
      transform: `translate(${position.x}px, ${position.y}px)`,
      pointerEvents: 'none',
      left: -20,
      top: -20,
      width: 40,
      height: 40,
    }} />
  );
}

Anda dapat membuat Hooks kustom, menggabungkannya bersama, mengirim data di antara keduanya, dan menggunakan kembali di antara komponen. Seiring dengan perkembangan aplikasi Anda, Anda akan menulis lebih sedikit Effects dengan tangan karena Anda akan mampu menggunakan kembali Hooks kustom yang Anda sudah tulis. Terdapat banyak Hook kustom yang luar biasa yang dikelola oleh komunitas React.

Siap mempelajari topik ini?

Baca Menggunakan kembali logika dengan Hooks kustom untuk mempelajari bagaimana membagi logika antara komponen.

Baca Lebih Lanjut

Apa selanjutnya?

Pergi ke Mereferensikan nilai menggunakan refs untuk memulai membaca bab ini halaman demi halaman!