Mengelola State

Intermediate

Seiring dengan bertumbuhnya aplikasi Anda, penting untuk lebih memperhatikan bagaimana state Anda diorganisasi dan bagaimana data mengalir di antara komponen-komponen Anda. State yang redundan atau duplikat adalah sumber umum dari bug. Di bab ini, Anda akan belajar bagaimana menstrukturkan state dengan baik, bagaimana menjaga logika pembaruan state agar mudah dikelola, dan bagaimana berbagi state antara komponen-komponen yang jauh.

Merespon masukan dengan State

Dalam React, Anda tidak akan mengubah UI dari kode secara langsung. Misalnya, Anda tidak akan menulis perintah seperti “nonaktifkan tombol”, “aktifkan tombol”, “tampilkan pesan sukses”, dll. Sebaliknya, Anda akan menggambarkan UI yang ingin Anda lihat untuk berbagai states visual dari komponen Anda (”state awal”, ”state mengetik”, ”state sukses”), dan kemudian memicu perubahan state sebagai respons terhadap masukan pengguna. Hal ini mirip dengan bagaimana desainer memikirkan tentang UI.

Berikut adalah sebuah formulir kuis yang dibangun menggunakan React. Perhatikan bagaimana ia menggunakan variabel state status untuk menentukan apakah tombol kirim diaktifkan atau dinonaktifkan, dan apakah pesan sukses ditampilkan sebagai gantinya.

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing');

  if (status === 'success') {
    return <h1>Itu Benar!</h1>
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <h2>Kuis Kota</h2>
      <p>
        Di kota manakah terdapat papan reklame yang mengubah udara menjadi air minum?
      </p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={
          answer.length === 0 ||
          status === 'submitting'
        }>
          Submit
        </button>
        {error !== null &&
          <p className="Error">
            {error.message}
          </p>
        }
      </form>
    </>
  );
}

function submitForm(answer) {
  // Pretend it's hitting the network.
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== 'lima'
      if (shouldError) {
        reject(new Error('Tebakan yang bagus tetapi jawaban salah. Silahkan coba lagi!'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

Ready to learn this topic?

Baca Reacting to Input with State untuk belajar bagaimana mendekati interaksi dengan mindset yang didorong oleh state.

Read More

Memilih Struktur State

Mengatur struktur state dengan baik dapat membuat perbedaan antara komponen yang mudah dimodifikasi dan didebug, dan komponen yang menjadi sumber kesalahan yang konstan. Prinsip paling penting adalah bahwa state tidak boleh mengandung informasi yang tidak perlu atau duplikat. Jika ada state yang tidak perlu, mudah untuk lupa untuk memperbarui state tersebut, dan memperkenalkan kesalahan!

Misalnya, formulir ini memiliki variabel state fullName yang redundan:

import { useState } from 'react';

export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  const [fullName, setFullName] = useState('');

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
    setFullName(e.target.value + ' ' + lastName);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
    setFullName(firstName + ' ' + e.target.value);
  }

  return (
    <>
      <h2>Izinkan kami memeriksa Anda</h2>
      <label>
        Nama depan:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Nama belakang:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        Tiket Anda akan diberikan kepada: <b>{fullName}</b>
      </p>
    </>
  );
}

Anda dapat menghapusnya dan menyederhanakan kode dengan menghitung fullName saat komponen di-render:

import { useState } from 'react';

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

  const fullName = firstName + ' ' + lastName;

  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }

  function handleLastNameChange(e) {
    setLastName(e.target.value);
  }

  return (
    <>
      <h2>Izinkan kami memeriksa Anda</h2>
      <label>
        Nama depan:{' '}
        <input
          value={firstName}
          onChange={handleFirstNameChange}
        />
      </label>
      <label>
        Nama belakang:{' '}
        <input
          value={lastName}
          onChange={handleLastNameChange}
        />
      </label>
      <p>
        Tiket Anda akan diberikan kepada: <b>{fullName}</b>
      </p>
    </>
  );
}

Ini mungkin terlihat seperti perubahan kecil, tetapi banyak bug pada aplikasi React dapat diperbaiki dengan cara ini.

Ready to learn this topic?

Baca Choosing the State Structure untuk belajar cara merancang bentuk state untuk menghindari kesalahan (bugs).

Read More

Berbagi State Antar Komponen

Terkadang, Anda ingin state dari dua komponen selalu berubah bersama. Untuk melakukannya, hapus state dari keduanya, pindahkan state tersebut ke induk (parent) paling dekat yang bersamaan, dan kemudian teruskan ke kedua komponen melalui props. Hal ini dikenal sebagai “mengangkat state ke atas” (lifting state up), dan ini adalah salah satu hal yang paling umum yang akan Anda lakukan saat menulis kode React.

Pada contoh ini, hanya satu panel yang harus aktif pada satu waktu. Untuk mencapainya, daripada menyimpan state aktif di setiap panel secara individu, komponen parent menyimpan state dan menentukan props untuk anak-anaknya.

import { useState } from 'react';

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel
        title="About"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        Dengan populasi sekitar 2 juta orang, Almaty adalah kota terbesar di Kazakhstan. Dari tahun 1929 hingga 1997, kota ini menjadi ibu kota Kazakhstan.
      </Panel>
      <Panel
        title="Etymology"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        Nama "Almaty" berasal dari kata <span lang="kk-KZ">алма</span>,dalam bahasa Kazakh yang berarti "apel"dan sering diterjemahkan sebagai "penuh dengan apel". Sebenarnya, wilayah sekitar Almaty dipercaya sebagai asal usul apel, dan <i lang="la">Malus sieversii</i> liar dianggap sebagai kandidat yang mungkin menjadi nenek moyang apel domestik modern.
      </Panel>
    </>
  );
}

function Panel({
  title,
  children,
  isActive,
  onShow
}) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={onShow}>
          Tampilkan
        </button>
      )}
    </section>
  );
}

Ready to learn this topic?

Baca Berbagi State Antar Komponen untuk mempelajari cara mengangkat state ke atas dan menjaga sinkronisasi antar komponen.

Read More

Memperjelas dan Mengatur Ulang State

Saat Anda me-render ulang sebuah komponen, React perlu memutuskan bagian mana dari pohon untuk dipertahankan (dan diperbarui), serta bagian mana yang harus dibuang atau dibuat kembali dari awal. Pada kebanyakan kasus, perilaku otomatis React sudah cukup baik. Secara default, React mempertahankan bagian-bagian pohon yang “cocok” dengan pohon komponen yang sebelumnya di-render.

Namun, terkadang ini bukan yang Anda inginkan. Dalam aplikasi obrolan ini, mengetik pesan dan kemudian mengubah penerima tidak akan mengatur ulang input. Hal ini dapat membuat pengguna secara tidak sengaja mengirim pesan ke orang yang salah:

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat contact={to} />
    </div>
  )
}

const contacts = [
  { name: 'Taylor', email: 'taylor@mail.com' },
  { name: 'Alice', email: 'alice@mail.com' },
  { name: 'Bob', email: 'bob@mail.com' }
];

React memungkinkan Anda untuk mengesampingkan perilaku default, dan memaksa sebuah komponen untuk mengatur ulang statusnya (state) dengan memberikan key yang berbeda, seperti <Chat key={email} />. Hal ini memberitahu React bahwa jika penerima berbeda, itu harus dianggap sebagai komponen Chat yang berbeda yang perlu dibuat kembali dari awal dengan data (dan UI seperti input) yang baru. Sekarang, beralih antara penerima mengatur ulang input - meskipun Anda me-render komponen yang sama.

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.email} contact={to} />
    </div>
  )
}

const contacts = [
  { name: 'Taylor', email: 'taylor@mail.com' },
  { name: 'Alice', email: 'alice@mail.com' },
  { name: 'Bob', email: 'bob@mail.com' }
];

Ready to learn this topic?

Baca Preserving and Resetting State untuk mempelajari masa hidup status dan cara mengendalikannya.

Read More

Mengekstrak logika State ke dalam Reducer

Komponen dengan banyak pembaruan state yang tersebar di banyak event handler dapat menjadi sangat membingungkan. Untuk kasus-kasus ini, Anda dapat mengkonsolidasikan semua logika pembaruan state di luar komponen Anda dalam sebuah fungsi tunggal, yang disebut ”reducer“. Event handler Anda menjadi lebih ringkas karena hanya menentukan “aksi” pengguna. Di bagian bawah file, fungsi reducer menentukan bagaimana state harus diperbarui sebagai respons terhadap setiap aksi!

import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';

export default function TaskApp() {
  const [tasks, dispatch] = useReducer(
    tasksReducer,
    initialTasks
  );

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  return (
    <>
      <h1>Rencana perjalanan Praha</h1>
      <AddTask
        onAddTask={handleAddTask}
      />
      <TaskList
        tasks={tasks}
        onChangeTask={handleChangeTask}
        onDeleteTask={handleDeleteTask}
      />
    </>
  );
}

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added': {
      return [...tasks, {
        id: action.id,
        text: action.text,
        done: false
      }];
    }
    case 'changed': {
      return tasks.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return tasks.filter(t => t.id !== action.id);
    }
    default: {
      throw Error('Unknown action: ' + action.type);
    }
  }
}

let nextId = 3;
const initialTasks = [
  { id: 0, text: 'Mengunjungi Musium Kafka', done: true },
  { id: 1, text: 'Menonton Pertujukan Boneka', done: false },
  { id: 2, text: 'Foto Tembok Lennon', done: false }
];

Ready to learn this topic?

Baca Extracting State Logic into a Reducer untuk mempelajari cara mengkonsolidasikan logika dalam fungsi reducer.

Read More

Melewatkan data secara dalam dengan Context

Biasanya, Anda akan melewatkan informasi dari komponen induk ke komponen anak (children) melalui props. Namun, melewatkan props dapat menjadi merepotkan jika Anda perlu melewatkan beberapa prop melalui banyak komponen, atau jika banyak komponen membutuhkan informasi yang sama. Context memungkinkan komponen induk membuat beberapa informasi tersedia untuk setiap komponen di bawahnya—tidak peduli seberapa dalam itu—tanpa melewatkan secara eksplisit melalui props.

Di sini, komponen Heading menentukan tingkat judulnya dengan “bertanya” pada Section terdekat untuk tingkatnya. Setiap Section melacak tingkatnya sendiri dengan bertanya pada Section induk dan menambahkan satu. Setiap Section menyediakan informasi kepada semua komponen di bawahnya tanpa melewatkan props—itu dilakukan melalui context.

import Heading from './Heading.js';
import Section from './Section.js';

export default function Page() {
  return (
    <Section>
      <Heading>Judul</Heading>
      <Section>
        <Heading>Judul</Heading>
        <Heading>Judul</Heading>
        <Heading>Judul</Heading>
        <Section>
          <Heading>Sub-judul</Heading>
          <Heading>Sub-judul</Heading>
          <Heading>Sub-judul</Heading>
          <Section>
            <Heading>Sub-sub-judul</Heading>
            <Heading>Sub-sub-judul</Heading>
            <Heading>Sub-sub-judul</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

Ready to learn this topic?

Baca Passing Data Deeply with Context untuk mempelajari penggunaan context sebagai alternatif dari melewatkan props.

Read More

Peningkatan Skala dengan Reducer dan Context

Reducer memungkinkan Anda mengonsolidasikan logika pembaruan state dari sebuah komponen. Context memungkinkan Anda melewatkan informasi ke komponen lain secara dalam. Anda dapat menggabungkan reducer dan context bersama-sama untuk mengelola state dari layar yang kompleks.

Dengan pendekatan ini, sebuah komponen induk dengan state yang kompleks dikelola dengan reducer. Komponen lain di dalam tree dapat membaca state-nya melalui context. Mereka juga dapat melakukan dispatch tindakan untuk memperbarui state tersebut.

import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import { TasksProvider } from './TasksContext.js';

export default function TaskApp() {
  return (
    <TasksProvider>
      <h1>Hari libur di Kyoto</h1>
      <AddTask />
      <TaskList />
    </TasksProvider>
  );
}

Ready to learn this topic?

Baca Peningkatan Skala dengan Reducer dan Context untuk mempelajari bagaimana pengelolaan state mengembang pada aplikasi yang berkembang.

Read More

Apa selanjutnya?

Lanjut ke halaman Reacting to Input with State untuk mulai membaca bab ini halaman per halaman!

Atau, jika Anda sudah familiar dengan topik-topik ini, mengapa tidak membaca tentang Escape Hatches?