Managing State

Intermediate

Ketika aplikasi Anda berkembang, penting untuk lebih memperhatikan bagaimana keadaan (state) Anda diorganisasi dan bagaimana aliran data antara komponen-komponen Anda. Keadaan yang redundan atau duplikat adalah sumber umum dari bug. Di bab ini, Anda akan belajar bagaimana mengatur keadaan (state) dengan baik, bagaimana menjaga logika pembaruan keadaan (state) agar mudah dikelola, dan bagaimana berbagi keadaan (state) antara komponen-komponen yang jauh.

Merespon masukan dengan keadaan (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 keadaan visual dari komponen Anda (“keadaan awal”, “keadaan mengetik”, “keadaan sukses”), dan kemudian memicu perubahan keadaan 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 keadaan (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 Merespon Masukan dengan Keadaan (State) untuk belajar bagaimana mendekati interaksi dengan mindset yang didorong oleh keadaan (state).

Read More

Memilih struktur keadaan (state)

Mengatur struktur keadaan 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 keadaan (state) tidak boleh mengandung informasi yang tidak perlu atau duplikat. Jika ada keadaan yang tidak perlu, mudah untuk lupa untuk memperbarui keadaan tersebut, dan memperkenalkan kesalahan!

Misalnya, formulir ini memiliki variabel keadaan 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 dirender:

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 kepadao: <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 Memilih Struktur Keadaan (State) untuk belajar cara merancang bentuk keadaan (state) untuk menghindari kesalahan (bugs).

Read More

Berbagi keadaan (state) antar komponen

Terkadang, Anda ingin keadaan (state) dari dua komponen selalu berubah bersama. Untuk melakukannya, hapus keadaan dari keduanya, pindahkan keadaan tersebut ke induk (parent) paling dekat yang bersamaan, dan kemudian teruskan ke kedua komponen melalui props. Hal ini dikenal sebagai “mengangkat keadaan (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 keadaan aktif di setiap panel secara individu, komponen parent menyimpan keadaan dan menentukan props untuk anak-anaknya (children).

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 Sharing State Between Components untuk mempelajari cara mengangkat state ke atas dan menjaga sinkronisasi antar komponen.

Read More

Memperjelas dan Mengatur Ulang Keadaan (state)

Saat Anda merender 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 dirender.

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 merender 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 Memperjelas dan Mengatur Ulang Keadaan (*state) untuk mempelajari masa hidup status dan cara mengendalikannya.

Read More

Mengekstrak logika keadaan (state) ke dalam reducer

Komponen dengan banyak pembaruan keadaan (state) yang tersebar di banyak event handler dapat menjadi sangat membingungkan. Untuk kasus-kasus ini, Anda dapat mengkonsolidasikan semua logika pembaruan keadaan (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 keadaan (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 Mengekstrak Logika Keadaan (State) ke dalam 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 (parent) 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 Melewatkan Data Secara Dalam dengan Context untuk mempelajari penggunaan context sebagai alternatif dari melewatkan props.

Read More

Mengembangkan Skala dengan Reducer dan Context

Reducer memungkinkan Anda mengonsolidasikan logika pembaruan keadaan (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 (parent) dengan keadaan (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 keadaan (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 Mengembangkan Skala dengan Reducer dan Context untuk mempelajari bagaimana pengelolaan keadaan (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?