Menambahkan Interaktivitas

Beberapa hal di layar berubah mengikuti masukan dari pengguna. Contohnya, mengeklik sebuah galeri gambar bisa mengganti gambar yang sedang aktif. Di React, data yang berubah seiring waktu disebut state. Anda dapat menambahkan state ke komponen apa pun, dan mengubahnya sesuai kebutuhan. Di bab ini, Anda akan belajar cara menulis komponen yang dapat menangani interaksi, mengubah state yang dimilikinya, dan menampilkan keluaran yang berbeda seiring berjalannya waktu.

Menanggapi events

React memungkinkan Anda untuk menambakan event handlers ke JSX Anda. Event handlers adalah fungsi milik Anda yang akan dipanggil sebagai respon terhadap interaksi dari pengguna seperti klik, hover, fokus pada masukan form, dan lain-lain.

Komponen bawaan seperti <button> hanya mendukung event bawaan dari peramban seperti onClick. Namun, Anda juga dapat membuat komponen Anda sendiri, dan memberikannya prop event handler dengan nama apa pun, spesifik terhadap aplikasi Anda.

export default function App() {
  return (
    <Toolbar
      onPlayMovie={() => alert('Memutar!')}
      onUploadImage={() => alert('Uploading!')}
    />
  );
}

function Toolbar({ onPlayMovie, onUploadImage }) {
  return (
    <div>
      <Button onClick={onPlayMovie}>
        Putar Film
      </Button>
      <Button onClick={onUploadImage}>
        Upload Gambar
      </Button>
    </div>
  );
}

function Button({ onClick, children }) {
  return (
    <button onClick={onClick}>
      {children}
    </button>
  );
}

Siap mempelajari topik ini?

Baca Menangani Event untuk mempelajari cara menambahkan event handler.

Baca Lebih Lanjut

State: Ingatan dari komponen

Komponen sering perlu mengubah apa yang ada di layar sebagai hasil dari sebuah interaksi. Mengetik kedalam form dapat mengubah sebuah kolom masukan, mengeklik “next” pada sebuah carousel gambar mengubah gambar yang sedang ditampilkan, mengeklik “beli” menambahkan sebuah produk kedalam keranjang belanja. Komponen perlu “mengingat” berbagai hal: nilai masukan saat ini, gambar saat ini, keranjang belanja. Di React, jenis ingatan komponen seperti ini disebut state.

Anda dapat menambahkan state kepada komponen dengan menggunakan Hook useState. Hooks adalah fungsi spesial yang memungkinkan komponen Anda untuk menggunakan fitur-fitur dari React (state adalah salah satu fitur tersebut). Hook useState memungkinkan Anda mendeklarasikan sebuah variabel state. Fungsi ini menerima state awal dan mengeluarkan sepasang nilai: state saat ini, dan sebuah fungsi state setter yang memungkinkan Anda untuk mengubah state tersebut.

const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);

Berikut adalah cara galeri gambar menggunakan dan mengubah state saat diklik:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  const hasNext = index < sculptureList.length - 1;

  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    } else {
      setIndex(0);
    }
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Selanjutnya
      </button>
      <h2>
        <i>{sculpture.name} </i>
        oleh {sculpture.artist}
      </h2>
      <h3>
        ({index + 1} dari {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Sembunyikan' : 'Tampilkan'} detail
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img
        src={sculpture.url}
        alt={sculpture.alt}
      />
    </>
  );
}

Siap mempelajari topik ini?

Baca State: Ingatan dari komponen untuk mempelajari cara untuk mengingat sebuah nilai dan mengubahnya saat berinteraksi.

Baca Lebih Lanjut

Render dan commit

Sebelum komponen Anda ditampilkan di layar, mereka harus di render oleh React. Memahami langkah-langkah dalam proses ini akan membantu Anda berpikir tentang bagaimana kode Anda akan dijalankan dan menjelaskan perilakunya.

Bayangkan bahwa komponen Anda adalah koki di dapur, menyusun hidangan lezat dari bahan-bahan dasar. Dalam skenario ini, React adalah pelayan yang mengajukan permintaan dari pelanggan dan membawakan pesanan mereka. Proses permintaan dan pelayanan UI ini memiliki tiga langkah:

  1. Memicu render (mengirimkan pesanan pelanggan ke dapur)
  2. Me-render komponen (menyiapkan pesanan di dapur)
  3. Commit ke DOM (menempatkan pesanan di meja)
  1. React sebagai pelayan di restoran, mengambil pesanan dari pengguna dan mengantarkannya ke Dapur Komponent
    Memicu
  2. Koki Card memberikan React komponen Card baru.
    Me-render
  3. React mengantarkan Card ke pengguna di meja mereka
    Commit

Ilustrasi oleh Rachel Lee Nabors

Siap mempelajari topik ini?

Baca Render dan Commit untuk mempelajari lifecycle dari perubahan UI.

Baca Lebih Lanjut

State sebagai snapshot

Tidak seperti variabel Javascript biasa, state di React berperilaku lebih seperti snapshot. Mengubah state tidaklah mengubah variabel state yang Anda miliki sekarang, tetapi akan memicu render ulang. Ini bisa mengejutkan pada awalnya!

console.log(count); // 0
setCount(count + 1); // Meminta render ulang dengan 1
console.log(count); // Masih 0!

Perilaku ini akan membantu Anda menghindari bug yang susah ditemukan. Berikut adalah aplikasi chat sederhana. Coba tebak apa yang terjadi jika Anda menekan “Kirim” terlebih dahulu dan kemudian mengubah penerima menjadi Bob. Nama siapa yang akan muncul di alert lima detik kemudian?

import { useState } from 'react';

export default function Form() {
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Halo');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`Anda mengatakan ${message} kepada ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Kepada:{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Kirim</button>
    </form>
  );
}

Siap mempelajari topik ini?

Baca State sebagai Snapshot untuk mempelajari mengapa state terlihat “tetap” dan tak berubah di dalam event handler.

Baca Lebih Lanjut

Mengantrikan serangkaian perubahan state

Komponent ini memiliki bug: mengeklik “+3” hanya akan menambahkan skor satu kali saja.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(score + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Skor: {score}</h1>
    </>
  )
}

State sebagai Snapshot menjelaskan mengapa ini terjadi. Mengubah state akan meminta render ulang baru, tetapi tidak akan mengubah state-nya di kode yang sudah berjalan. Jadi score tetap 0 setelah Anda memanggil setScore(score + 1).

console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0
setScore(score + 1); // setScore(0 + 1);
console.log(score); // 0

Anda bisa memperbaiki ini dengan memberikan updater function ketika mengubah state. Perhatikan bagaimana mengganti setScore(score + 1) dengan setScore(s => s + 1) memperbaiki tombol “+3”. Cara ini memungkinkan Anda untuk mengantrikan beberapa perubahan state.

import { useState } from 'react';

export default function Counter() {
  const [score, setScore] = useState(0);

  function increment() {
    setScore(s => s + 1);
  }

  return (
    <>
      <button onClick={() => increment()}>+1</button>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <h1>Skor: {score}</h1>
    </>
  )
}

Siap mempelajari topik ini?

Baca Mengantrikan serangkaian perubahan state untuk mempelajari cara meng-queue sebuah rentetan perubahan state.

Baca Lebih Lanjut

Mengubah object di dalam state

State bisa memegang berbagai jenis nilai JavaScript, termasuk object. Tetapi Anda tidak boleh mengubah object dan array yang Anda simpan di dalam state React secara langsung. Melainkan, ketika Anda ingin mengubah object dan array, Anda perlu membuat object yang baru (atau membuat salinan dari object yang sudah ada), dan kemudian mengubah state-nya untuk menggunakan salinan tersebut.

Biasanya, Anda akan menggunakan sintaks spreads ... untuk menyalin object dan array yang ingin Anda ubah. Contohnya, mengubah object yang bersarang bisa terlihat seperti ini:

import { useState } from 'react';

export default function Form() {
  const [person, setPerson] = useState({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    setPerson({
      ...person,
      name: e.target.value
    });
  }

  function handleTitleChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        title: e.target.value
      }
    });
  }

  function handleCityChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        city: e.target.value
      }
    });
  }

  function handleImageChange(e) {
    setPerson({
      ...person,
      artwork: {
        ...person.artwork,
        image: e.target.value
      }
    });
  }

  return (
    <>
      <label>
        Nama:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Judul:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        Kota:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Gambar:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' oleh '}
        {person.name}
        <br />
        (berada di {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

Jika menyalin object di kode terasa terlalu ribet, Anda bisa menggunakan library seperti Immer untuk mengurangi kode yang berulang-ulang:

import { useImmer } from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    updatePerson(draft => {
      draft.name = e.target.value;
    });
  }

  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }

  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }

  function handleImageChange(e) {
    updatePerson(draft => {
      draft.artwork.image = e.target.value;
    });
  }

  return (
    <>
      <label>
        Nama:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Judul:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        Kota:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Gambar:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' oleh '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img
        src={person.artwork.image}
        alt={person.artwork.title}
      />
    </>
  );
}

Siap mempelajari topik ini?

Baca Mengubah Object di dalam State untuk mempelajari cara mengubah object dengan benar.

Baca Lebih Lanjut

Mengubah array di dalam state

Array adalah tipe object lain yang bersifat mutable di Javascript yang bisa Anda simpan di state dan harus diperlakukan sebagai read-only. Seperti object, ketika Anda ingin mengubah array yang disimpan di state, Anda perlu membuat array baru (atau membuat salinan dari array yang sudah ada), dan kemudian mengubah state-nya untuk menggunakan array baru tersebut:

import { useState } from 'react';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, setList] = useState(
    initialList
  );

  function handleToggle(artworkId, nextSeen) {
    setList(list.map(artwork => {
      if (artwork.id === artworkId) {
        return { ...artwork, seen: nextSeen };
      } else {
        return artwork;
      }
    }));
  }

  return (
    <>
      <h1>Bucket List Seni</h1>
      <h2>Daftar seni untuk dilihat:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

Jika menyalin array di kode terasa terlalu ribet, Anda bisa menggunakan library seperti Immer untuk mengurangi kode yang berulang-ulang:

import { useState } from 'react';
import { useImmer } from 'use-immer';

let nextId = 3;
const initialList = [
  { id: 0, title: 'Big Bellies', seen: false },
  { id: 1, title: 'Lunar Landscape', seen: false },
  { id: 2, title: 'Terracotta Army', seen: true },
];

export default function BucketList() {
  const [list, updateList] = useImmer(initialList);

  function handleToggle(artworkId, nextSeen) {
    updateList(draft => {
      const artwork = draft.find(a =>
        a.id === artworkId
      );
      artwork.seen = nextSeen;
    });
  }

  return (
    <>
      <h1>Bucket List Seni</h1>
      <h2>Daftar seni untuk dilihat:</h2>
      <ItemList
        artworks={list}
        onToggle={handleToggle} />
    </>
  );
}

function ItemList({ artworks, onToggle }) {
  return (
    <ul>
      {artworks.map(artwork => (
        <li key={artwork.id}>
          <label>
            <input
              type="checkbox"
              checked={artwork.seen}
              onChange={e => {
                onToggle(
                  artwork.id,
                  e.target.checked
                );
              }}
            />
            {artwork.title}
          </label>
        </li>
      ))}
    </ul>
  );
}

Siap mempelajari topik ini?

Baca Mengubah Array di dalam State untuk mempelajari cara mengubah array dengan benar.

Baca Lebih Lanjut

Apa selanjutnya?

Lanjutkan ke Menanggapi Event untuk mulai membaca bab ini halaman demi halaman!

Atau, jika Anda sudah akrab dengan topik ini, mengapa tidak membaca tentang Mengatur State?