diff --git a/src/content/learn/escape-hatches.md b/src/content/learn/escape-hatches.md
index 2637fadc6..ecaa4c49a 100644
--- a/src/content/learn/escape-hatches.md
+++ b/src/content/learn/escape-hatches.md
@@ -97,9 +97,9 @@ Baca **[Manipulasi DOM dengan Refs](/learn/manipulating-the-dom-with-refs)** unt
-## Menyinkronkan dengan *Effects* {/*synchronizing-with-effects*/}
+## Sinkronisasi dengan *Effect* {/*synchronizing-with-effects*/}
-Beberapa komponen perlu menyinkronkan dengan sistem eksternal. Misalkan, Anda mungkin ingin mengontrol komponen *non-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 menyinkronkan komponen Anda dengan sistem di luar React.
+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`:
@@ -187,7 +187,7 @@ Di mode pengembangan (*development*), React akan segera menjalankan dan membersi
-Baca **[Menyinkronkan dengan *Effects*](/learn/synchronizing-with-effects)** untuk mempelajari bagaimana menyinkronkan komponen dengan sistem eksternal.
+Baca **[Sinkronisasi dengan *Effect*](/learn/synchronizing-with-effects)** untuk mempelajari bagaimana menyinkronkan komponen dengan sistem eksternal.
diff --git a/src/content/learn/synchronizing-with-effects.md b/src/content/learn/synchronizing-with-effects.md
index f1aa98438..116f2672c 100644
--- a/src/content/learn/synchronizing-with-effects.md
+++ b/src/content/learn/synchronizing-with-effects.md
@@ -1,97 +1,97 @@
---
-title: 'Synchronizing with Effects'
+title: 'Sinkronisasi dengan Effect'
---
-Some components need to synchronize with external systems. For example, you might want to control a non-React component based on the React state, set up a server connection, or send an analytics log when a component appears on the screen. *Effects* let you run some code after rendering so that you can synchronize your component with some system outside of React.
+Beberapa komponen perlu melakukan sinkronisasi dengan 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. *Effects* memungkinkan Anda menjalankan kode setelah *render* sehingga Anda bisa melakukan sinkronisasi dengan sistem di luar React.
-- What Effects are
-- How Effects are different from events
-- How to declare an Effect in your component
+- Apa itu *Effect*
+- Perbedaan *Effect* dengan *event*
+- Cara mendeklarasikan *Effect* di dalam komponen
- How to skip re-running an Effect unnecessarily
-- Why Effects run twice in development and how to fix them
+- Mengapa *Effect* berjalan dua kali di pengembangan (*development*) dan cara memperbaikinya
-## What are Effects and how are they different from events? {/*what-are-effects-and-how-are-they-different-from-events*/}
+## Apa itu *Effect* dan apa perbedaanya dengan *event*? {/*what-are-effects-and-how-are-they-different-from-events*/}
-Before getting to Effects, you need to be familiar with two types of logic inside React components:
+Sebelum kita membahas *Effect*, Anda perlu mengenal dua tipe logika di dalam komponen React:
-- **Rendering code** (introduced in [Describing the UI](/learn/describing-the-ui)) lives at the top level of your component. This is where you take the props and state, transform them, and return the JSX you want to see on the screen. [Rendering code must be pure.](/learn/keeping-components-pure) Like a math formula, it should only _calculate_ the result, but not do anything else.
+- **Kode pe-*render*-an** (diperkenalkan di [Menggambarkan Antarmuka Pengguna](/learn/describing-the-ui)) berada di tingkat atas komponen Anda. Inilah di mana Anda mengambil *props* dan *state*, mentransformasinya, dan mengembalikan JSX yang diinginkan di layar. [Kode pe-*render*-an haruslah murni.](/learn/keeping-components-pure) Seperti rumus matematika, ia harus _menghitung_ hasilnya saja, tapi tidak melakukan hal lainnya.
-- **Event handlers** (introduced in [Adding Interactivity](/learn/adding-interactivity)) are nested functions inside your components that *do* things rather than just calculate them. An event handler might update an input field, submit an HTTP POST request to buy a product, or navigate the user to another screen. Event handlers contain ["side effects"](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) (they change the program's state) caused by a specific user action (for example, a button click or typing).
+- ***Event handlers*** (diperkenalkan di [Menambahkan Interaktivitas](/learn/adding-interactivity)) adalah fungsi bersarang di dalam komponen yang *melakukan* berbagai hal dan bukan hanya menghitungnya. Sebuah *event handler* dapat memperbarui bidang input, mengirimkan permintaan HTTP POST untuk membeli produk, atau menavigasi pengguna ke layar lain. *Event handlers* memiliki ["efek samping"](https://en.wikipedia.org/wiki/Side_effect_(computer_science)) (yaitu mengubah *state* program) yang dihasilkan dari aksi pengguna tertentu (misalnya, tekanan tombol atau ketikan).
-Sometimes this isn't enough. Consider a `ChatRoom` component that must connect to the chat server whenever it's visible on the screen. Connecting to a server is not a pure calculation (it's a side effect) so it can't happen during rendering. However, there is no single particular event like a click that causes `ChatRoom` to be displayed.
+Terkadang hal-hal ini tidak cukup. Bayangkan sebuah komponen `ChatRoom` yang harus melakukan koneksi ke server obrolan (*chat*) ketika ditampilkan di layar. Melakukan koneksi ke server bukanlah penghitungan murni (melainkan efek samping) jadi tidak dapat dilakukan saat proses *render*. Meskipun itu, tidak ada *event* tertentu seperti klik yang akan menampilkan `ChatRoom`.
-***Effects* let you specify side effects that are caused by rendering itself, rather than by a particular event.** Sending a message in the chat is an *event* because it is directly caused by the user clicking a specific button. However, setting up a server connection is an *Effect* because it should happen no matter which interaction caused the component to appear. Effects run at the end of a [commit](/learn/render-and-commit) after the screen updates. This is a good time to synchronize the React components with some external system (like network or a third-party library).
+***Effects* memungkinkan Anda menentukan efek samping yang disebabkan oleh pe-*render*-an itu sendiri, dan bukan oleh *event* tertentu.** Mengirim pesan di ruang obrolan merupakan *event* karena disebabkan secara langsung oleh pengguna yang mengeklik tombol tertentu. Namun, melakukan koneksi server merupakan *Effect* karena harus terjadi tanpa peduli interaksi apapun yang menyebabkan komponen ditampilkan. *Effects* berjalan di akhir [*commit*](/learn/render-and-commit) setelah layar diperbarui. Ini merupakan waktu yang tepat untuk menyinkronkan komponen React dengan sistem eksternal (seperti jaringan atau pustaka pihak ketiga).
-Here and later in this text, capitalized "Effect" refers to the React-specific definition above, i.e. a side effect caused by rendering. To refer to the broader programming concept, we'll say "side effect".
+Di sini dan selanjutnya dalam teks ini, kata "*Effect*" yang dikapitalisasi mengacu kepada definisi khusus React yang dijelaskan di atas, seperti efek samping yang disebabkan oleh proses *render*. Untuk mengacu kepada konsep pemrograman secara keseluruhan, kita akan menggunakan kata "efek samping".
-## You might not need an Effect {/*you-might-not-need-an-effect*/}
+## Anda mungkin tidak membutuhkan *Effect* {/*you-might-not-need-an-effect*/}
-**Don't rush to add Effects to your components.** Keep in mind that Effects are typically used to "step out" of your React code and synchronize with some *external* system. This includes browser APIs, third-party widgets, network, and so on. If your Effect only adjusts some state based on other state, [you might not need an Effect.](/learn/you-might-not-need-an-effect)
+**Jangan terburu-buru menambahkan *Effects* ke dalam komponen Anda.** Perlu diingat bahwa *Effects* umumnya digunakan untuk "melangkah ke luar" dari kode React Anda dan menyinkronkan dengan sistem *eksternal*. Hal ini termasuk API peramban (*browser*), *widget* pihak ketiga, jaringan, dan lainnya. Apabila *Effect* Anda hanya mengatur *state* berdasarkan *state* lain, [Anda mungkin tidak membutuhkan *Effect*.](/learn/you-might-not-need-an-effect)
-## How to write an Effect {/*how-to-write-an-effect*/}
+## Cara menulis *Effect* {/*how-to-write-an-effect*/}
-To write an Effect, follow these three steps:
+Untuk menulis *Effect*, ikuti tiga langkah berikut:
-1. **Declare an Effect.** By default, your Effect will run after every render.
-2. **Specify the Effect dependencies.** Most Effects should only re-run *when needed* rather than after every render. For example, a fade-in animation should only trigger when a component appears. Connecting and disconnecting to a chat room should only happen when the component appears and disappears, or when the chat room changes. You will learn how to control this by specifying *dependencies.*
-3. **Add cleanup if needed.** Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, "connect" needs "disconnect", "subscribe" needs "unsubscribe", and "fetch" needs either "cancel" or "ignore". You will learn how to do this by returning a *cleanup function*.
+1. **Deklarasikan Effect.** Secara bawaan, *Effect* Anda akan berjalan setiap *render*.
+2. **Tentukan dependensi dari Effect.** Kebanyakan *Effect* hanya perlu dijalankan ulang *ketika diperlukan*, bukan setiap render. Misalnya, animasi *fade-in* seharusnya hanya dijalankan ketika sebuah komponen muncul. Menghubungkan dan memutuskan koneksi ke ruang obrolan seharusnya hanya terjadi ketika komponen muncul dan menghilang, atau ketika ruang obrolan berubah. Anda akan belajar cara mengontrolnya dengan menentukan *dependensi.*
+3. **Tambahkan pembersihan (*cleanup*) jika diperlukan.** Beberapa *Effect* perlu menentukan cara menghentikan, membatalkan, atau membersihkan apa pun yang sedang dilakukan. Misalnya, "sambungkan koneksi" membutuhkan "lepaskan koneksi", "berlangganan" memerlukan "hentikan langganan", dan "*fetch*" membutuhkan "batal" atau "abaikan". Anda akan belajar cara melakukan hal tersebut dengan mengembalikan *fungsi pembersihan*.
-Let's look at each of these steps in detail.
+Mari kita lihat langkah-langkah berikut secara detil.
-### Step 1: Declare an Effect {/*step-1-declare-an-effect*/}
+### Langkah 1: Deklarasikan Effect {/*step-1-declare-an-effect*/}
-To declare an Effect in your component, import the [`useEffect` Hook](/reference/react/useEffect) from React:
+Untuk mendeklarasikan *Effect* di dalam komponen, impor [Hook `useEffect`](/reference/react/useEffect) dari React:
```js
import { useEffect } from 'react';
```
-Then, call it at the top level of your component and put some code inside your Effect:
+Kemudian, panggil Hook tersebut di atas komponen Anda dan isikan *Effect* tersebut dengan kode:
```js {2-4}
function MyComponent() {
useEffect(() => {
- // Code here will run after *every* render
+ // Kode di dalam blok ini akan dijalankan setelah *setiap* render
});
return
;
}
```
-Every time your component renders, React will update the screen *and then* run the code inside `useEffect`. In other words, **`useEffect` "delays" a piece of code from running until that render is reflected on the screen.**
+Setiap kali setelah komponen Anda di-*render*, React akan memperbarui layar *kemudian* menjalankan kode di dalam `useEffect`. Dengan kata lain, **`useEffect` "menunda" sepotong kode agar tidak berjalan sampai *render* tersebut ditampilkan di layar.**
-Let's see how you can use an Effect to synchronize with an external system. Consider a `` React component. It would be nice to control whether it's playing or paused by passing an `isPlaying` prop to it:
+Mari kita lihat bagaimana Anda dapat menggunakan *Effect* untuk melakukan sinkronisasi dengan sistem eksternal. Bayangkan sebuah komponen React ``. Akan lebih baik jika kita dapat mengontrol apakah video sedang diputar atau dijeda dengan mengoper *prop* `isPlaying` ke dalamnya:
```js
;
```
-Your custom `VideoPlayer` component renders the built-in browser [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) tag:
+Komponen `VideoPlayer` kustom Anda me-*render* tag bawaan peramban [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video):
```js
function VideoPlayer({ src, isPlaying }) {
- // TODO: do something with isPlaying
+ // TODO: lakukan sesuatu dengan isPlaying
return ;
}
```
-However, the browser `` tag does not have an `isPlaying` prop. The only way to control it is to manually call the [`play()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) and [`pause()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause) methods on the DOM element. **You need to synchronize the value of `isPlaying` prop, which tells whether the video _should_ currently be playing, with calls like `play()` and `pause()`.**
+Namun, tag `` pada peramban tidak memiliki *prop* `isPlaying`. Satu-satunya cara untuk mengontrolnya adalah untuk memanggil metode [`play()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play) dan [`pause()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause) dalam elemen DOM secara manual. **Anda perlu menyinkronkan nilai *prop* `isPlaying`, yang memberitahu apakah video _seharusnya_ sedang diputar, dengan panggilan metode seperti `play()` dan `pause()`.**
-We'll need to first [get a ref](/learn/manipulating-the-dom-with-refs) to the `` DOM node.
+Pertama-tama, kita perlu [mendapatkan *ref*](/learn/manipulating-the-dom-with-refs) ke simpul DOM ``.
-You might be tempted to try to call `play()` or `pause()` during rendering, but that isn't correct:
+Anda mungkin tergoda untuk mencoba memanggil `play()` atau `pause()` saat pe-*render*-an, tapi ini tidak benar:
@@ -102,9 +102,9 @@ function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);
if (isPlaying) {
- ref.current.play(); // Calling these while rendering isn't allowed.
+ ref.current.play(); // Memanggil ini saat rendering tidak diperbolehlkan.
} else {
- ref.current.pause(); // Also, this crashes.
+ ref.current.pause(); // Ini juga akan menyebabkan *crash*.
}
return ;
@@ -133,11 +133,11 @@ video { width: 250px; }
-The reason this code isn't correct is that it tries to do something with the DOM node during rendering. In React, [rendering should be a pure calculation](/learn/keeping-components-pure) of JSX and should not contain side effects like modifying the DOM.
+Alasan kode ini tidak benar adalah ia mencoba melakukan sesuati dengan simpul DOM saat proses *render*. Dalam React, [proses render harus merupakan penghitungan murni](/learn/keeping-components-pure) dari JSX dan tidak boleh mengandung efek samping seperti memodifikasi DOM.
-Moreover, when `VideoPlayer` is called for the first time, its DOM does not exist yet! There isn't a DOM node yet to call `play()` or `pause()` on, because React doesn't know what DOM to create until you return the JSX.
+Lebih dari itu, ketika `VideoPlayer` dipanggil pertama kalinya, DOM belum tersedia! Belum ada simpul DOM untuk memanggil `play()` atau `pause()`, karena React tidak mengetahui DOM apa yang perlu dibuat sampai Anda mengembalikan JSX.
-The solution here is to **wrap the side effect with `useEffect` to move it out of the rendering calculation:**
+Solusinya adalah **membungkus efek samping dengan `useEffect` memindahkannya keluar dari penghitungan proses *render*:**
```js {6,12}
import { useEffect, useRef } from 'react';
@@ -157,11 +157,11 @@ function VideoPlayer({ src, isPlaying }) {
}
```
-By wrapping the DOM update in an Effect, you let React update the screen first. Then your Effect runs.
+Dengan membungkus pembaruan DOM di dalam *Effect*, Anda memungkinkan React memperbarui layar terlebih dahulu. Kemudian *Effect* Anda dijalankan.
-When your `VideoPlayer` component renders (either the first time or if it re-renders), a few things will happen. First, React will update the screen, ensuring the `` tag is in the DOM with the right props. Then React will run your Effect. Finally, your Effect will call `play()` or `pause()` depending on the value of `isPlaying`.
+Ketika komponen `VideoPlayer` Anda di-*render* (baik pertama kalinya atau ketika di-*render* ulang), beberapa hal akan terjadi. Pertama, React akan memperbarui layar, memastikan tag `` berada di dalam DOM dengan *props* yang benar. Kemudian React akan menjalankan *Effect* Anda. Pada akhirnya, *Effect* Anda akan memanggil `play()` atau `pause()` berdasarkan nilai dari `isPlaying`.
-Press Play/Pause multiple times and see how the video player stays synchronized to the `isPlaying` value:
+Coba tekan Putar/Jeda beberapa kali dan lihat bagaimana pemutar video tetap tersinkron dengan nilai `isPlaying`:
@@ -187,7 +187,7 @@ export default function App() {
return (
<>
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? 'Jeda' : 'Putar'}
-In this example, the "external system" you synchronized to React state was the browser media API. You can use a similar approach to wrap legacy non-React code (like jQuery plugins) into declarative React components.
+Dalam contoh ini, "sistem eksternal" yang Anda sinkronisasi ke *state* React adalah API media peramban. Anda dapat menggunakan pendekatan sama untuk membungkus kode lama di luar React (seperti *plugin* jQuery) ke komponen deklaratif React.
-Note that controlling a video player is much more complex in practice. Calling `play()` may fail, the user might play or pause using the built-in browser controls, and so on. This example is very simplified and incomplete.
+Perlu dicatat bahwa mengontrol pemutar video jauh lebih kompleks dalam praktiknya. Memanggil `play()` bisa gagal, pengguna dapat memutar atau menjeda menggunakan kontrol peramban bawaan, dan sebagainya. Contoh ini sangat disederhanakan dan tidak lengkap.
-By default, Effects run after *every* render. This is why code like this will **produce an infinite loop:**
+Secara bawaan, *Effects* dijalankan setelah *setiap* render. Inilah sebabnya mengapa kode seperti ini akan **menghasilkan perulangan tak terbatas (*infinite loop*):**
```js
const [count, setCount] = useState(0);
@@ -220,20 +220,22 @@ useEffect(() => {
});
```
-Effects run as a *result* of rendering. Setting state *triggers* rendering. Setting state immediately in an Effect is like plugging a power outlet into itself. The Effect runs, it sets the state, which causes a re-render, which causes the Effect to run, it sets the state again, this causes another re-render, and so on.
+*Effects* dijalankan sebagai *hasil* rendering. Mengatur *state* *memicu* rendering. Mengatur *state* secara langsung dalam suatu *Effect*, seperti mencolokkan stopkontak ke stopkontak itu sendiri. *Effect* berjalan, mengatur *state*, yang menyebabkan *render* ulang, yang menyebabkan *Effect* berjalan, mengatur *state* lagi, yang menyebabkan *render* ulang, dan seterusnya.
+
+Perlu diingat bahwa *Effects* umumnya digunakan untuk "melangkah ke luar" dari kode React Anda dan menyinkronkan dengan sistem *eksternal*. Hal ini termasuk API peramban (*browser*), *widget* pihak ketiga, jaringan, dan lainnya. Apabila *Effect* Anda hanya mengatur *state* berdasarkan *state* lain, [Anda mungkin tidak membutuhkan *Effect*.](/learn/you-might-not-need-an-effect)
-Effects should usually synchronize your components with an *external* system. If there's no external system and you only want to adjust some state based on other state, [you might not need an Effect.](/learn/you-might-not-need-an-effect)
+*Effects* biasanya *hanya* digunakan untuk menyinkronkan komponen Anda dengan sistem *eksternal*. Jika tidak ada sistem eksternal dan Anda hanya ingin mengatur *state* berdasarkan *state* lain, [Anda mungkin tidak membutuhkan *Effect*.](/learn/you-might-not-need-an-effect)
-### Step 2: Specify the Effect dependencies {/*step-2-specify-the-effect-dependencies*/}
+### Langkah 2: Tentukan dependensi dari *Effect* {/*step-2-specify-the-effect-dependencies*/}
-By default, Effects run after *every* render. Often, this is **not what you want:**
+Secara bawaan, *Effects* berjalan setelah *setiap* *render*. Seringkali, ini **bukan yang Anda inginkan:**
-- Sometimes, it's slow. Synchronizing with an external system is not always instant, so you might want to skip doing it unless it's necessary. For example, you don't want to reconnect to the chat server on every keystroke.
-- Sometimes, it's wrong. For example, you don't want to trigger a component fade-in animation on every keystroke. The animation should only play once when the component appears for the first time.
+- Terkadang, lambat. Sinkronisasi dengan sistem eksternal tidak selalu instan, jadi Anda mungkin ingin melewatkannya kecuali jika diperlukan. Misalnya, Anda tidak ingin menyambung kembali ke server obrolan pada setiap penekanan papan ketik.
+- Terkadang, tidak benar. Misalnya, Anda tidak ingin memicu animasi *fade-in* komponen pada setiap penekanan papan ketik. Animasi seharusnya hanya diputar satu kali ketika komponen muncul untuk pertama kalinya.
-To demonstrate the issue, here is the previous example with a few `console.log` calls and a text input that updates the parent component's state. Notice how typing causes the Effect to re-run:
+Untuk mendemonstrasikan masalah ini, berikut adalah contoh sebelumnya dengan beberapa panggilan `console.log` dan input teks yang memperbarui *state* komponen induk. Perhatikan bagaimana pengetikan menyebabkan *Effect* dijalankan kembali:
@@ -245,10 +247,10 @@ function VideoPlayer({ src, isPlaying }) {
useEffect(() => {
if (isPlaying) {
- console.log('Calling video.play()');
+ console.log('Memanggil video.play()');
ref.current.play();
} else {
- console.log('Calling video.pause()');
+ console.log('Memanggil video.pause()');
ref.current.pause();
}
});
@@ -263,7 +265,7 @@ export default function App() {
<>
setText(e.target.value)} />
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? 'Jeda' : 'Putar'}
-You can tell React to **skip unnecessarily re-running the Effect** by specifying an array of *dependencies* as the second argument to the `useEffect` call. Start by adding an empty `[]` array to the above example on line 14:
+Anda dapat memberi tahu React untuk **melewatkan menjalankan ulang *Effect* yang tidak perlu** dengan menspesifikasikan senarai *dependencies* sebagai argumen kedua pada pemanggilan `useEffect`. Mulai dengan menambahkan senarai kosong `[]` ke dalam contoh di atas pada baris 14:
```js {3}
useEffect(() => {
@@ -289,7 +291,7 @@ You can tell React to **skip unnecessarily re-running the Effect** by specifying
}, []);
```
-You should see an error saying `React Hook useEffect has a missing dependency: 'isPlaying'`:
+Anda akan melihat *error* yang mengatakan `React Hook useEffect has a missing dependency: 'isPlaying'`:
@@ -301,10 +303,10 @@ function VideoPlayer({ src, isPlaying }) {
useEffect(() => {
if (isPlaying) {
- console.log('Calling video.play()');
+ console.log('Memanggil video.play()');
ref.current.play();
} else {
- console.log('Calling video.pause()');
+ console.log('Memanggil video.pause()');
ref.current.pause();
}
}, []); // This causes an error
@@ -319,7 +321,7 @@ export default function App() {
<>
setText(e.target.value)} />
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? 'Jeda' : 'Putar'}
-The problem is that the code inside of your Effect *depends on* the `isPlaying` prop to decide what to do, but this dependency was not explicitly declared. To fix this issue, add `isPlaying` to the dependency array:
+Masalahnya adalah kode di dalam Effect Anda *tergantung pada* *prop* `isPlaying` untuk memutuskan apa yang harus dilakukan, tetapi ketergantungan ini tidak dideklarasikan secara eksplisit. Untuk memperbaiki masalah ini, tambahkan `isPlaying` ke dalam senarai dependensi:
```js {2,7}
useEffect(() => {
- if (isPlaying) { // It's used here...
+ if (isPlaying) { // Digunakan di sini...
// ...
} else {
// ...
}
- }, [isPlaying]); // ...so it must be declared here!
+ }, [isPlaying]); // ...jadi harus dideklarasikan di sini!
```
-Now all dependencies are declared, so there is no error. Specifying `[isPlaying]` as the dependency array tells React that it should skip re-running your Effect if `isPlaying` is the same as it was during the previous render. With this change, typing into the input doesn't cause the Effect to re-run, but pressing Play/Pause does:
+Sekarang semua dependensi dideklarasikan, jadi tidak ada *error*. Menentukan `[isPlaying]` sebagai senarai dependensi memberi tahu React ia harus melewati menjalankan ulang *Effect* Anda apabila `isPlaying` sama seperti saat *render* sebelumnya. Dengan perubahan ini, mengetik pada input tidak menyebabkan *Effect* dijalankan ulang, tapi menekan Putar/Jeda akan menyebabkannya:
@@ -361,10 +363,10 @@ function VideoPlayer({ src, isPlaying }) {
useEffect(() => {
if (isPlaying) {
- console.log('Calling video.play()');
+ console.log('Memanggil video.play()');
ref.current.play();
} else {
- console.log('Calling video.pause()');
+ console.log('Memanggil video.pause()');
ref.current.pause();
}
}, [isPlaying]);
@@ -379,7 +381,7 @@ export default function App() {
<>
setText(e.target.value)} />
setIsPlaying(!isPlaying)}>
- {isPlaying ? 'Pause' : 'Play'}
+ {isPlaying ? 'Jeda' : 'Putar'}
-The dependency array can contain multiple dependencies. React will only skip re-running the Effect if *all* of the dependencies you specify have exactly the same values as they had during the previous render. React compares the dependency values using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison. See the [`useEffect` reference](/reference/react/useEffect#reference) for details.
+Senarai dependensi dapat berisi lebih dari satu dependensi. React hanya akan melewatkan menjalankan ulang *Effect* jika *semua* dependensi yang Anda tentukan memiliki nilai yang sama persis dengan nilai yang mereka miliki saat render sebelumnya. React membandingkan nilai dependensi menggunakan fungsi pembanding [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). Lihat [referensi `useEffect`](/reference/react/useEffect#reference) untuk detailnya.
-**Notice that you can't "choose" your dependencies.** You will get a lint error if the dependencies you specified don't match what React expects based on the code inside your Effect. This helps catch many bugs in your code. If you don't want some code to re-run, [*edit the Effect code itself* to not "need" that dependency.](/learn/lifecycle-of-reactive-effects#what-to-do-when-you-dont-want-to-re-synchronize)
+**Perhatikan bahwa Anda tidak dapat "memilih" dependensi Anda.** Anda akan mendapatkan *lint error* jika dependensi yang Anda tentukan tidak sesuai dengan apa yang diharapkan oleh React berdasarkan kode di dalam *Effect* Anda. Hal ini membantu menangkap banyak *bug* dalam kode Anda. Jika Anda tidak ingin beberapa kode dijalankan ulang, [*edit kode Effect itu sendiri* untuk tidak "membutuhkan" dependensi tersebut.](/learn/lifecycle-of-reactive-effects#what-to-do-when-you-dont-want-to-re-synchronize)
-The behaviors without the dependency array and with an *empty* `[]` dependency array are different:
+Perilaku tanpa senarai dependensi dan dengan senarai dependensi *kosong* `[]` berbeda:
```js {3,7,11}
useEffect(() => {
- // This runs after every render
+ // Ini dijalankan setiap render
});
useEffect(() => {
- // This runs only on mount (when the component appears)
+ // Ini hanya dijalankan setiap pemasangan (ketika komponen ditampilkan)
}, []);
useEffect(() => {
- // This runs on mount *and also* if either a or b have changed since the last render
+ // Ini dijalankan setiap pemasangan *dan juga* ketika a atau b telah berubah sejak render sebelumnya
}, [a, b]);
```
-We'll take a close look at what "mount" means in the next step.
+Kita akan mencermati secara dekat, apa arti "pemasangan" dalam langkah berikutnya.
-#### Why was the ref omitted from the dependency array? {/*why-was-the-ref-omitted-from-the-dependency-array*/}
+#### Mengapa ref dihilangkan dari senarai dependensi? {/*why-was-the-ref-omitted-from-the-dependency-array*/}
-This Effect uses _both_ `ref` and `isPlaying`, but only `isPlaying` is declared as a dependency:
+*Effect* ini menggunakan `ref` *dan* `isPlaying`, tapi hanya `isPlaying` yang dideklarasikan sebagai dependensi:
```js {9}
function VideoPlayer({ src, isPlaying }) {
@@ -441,7 +443,7 @@ function VideoPlayer({ src, isPlaying }) {
}, [isPlaying]);
```
-This is because the `ref` object has a *stable identity:* React guarantees [you'll always get the same object](/reference/react/useRef#returns) from the same `useRef` call on every render. It never changes, so it will never by itself cause the Effect to re-run. Therefore, it does not matter whether you include it or not. Including it is fine too:
+Hal ini dikarenakan objek `ref` memiliki *identitas yang stabil:* React menjamin [Anda akan selalu mendapatkan objek yang sama](/reference/react/useRef#returns) dari pemanggilan `useRef` yang sama pada setiap render. Objek tersebut tidak pernah berubah, sehingga tidak akan pernah dengan sendirinya menyebabkan *Effect* dijalankan ulang. Oleh karena itu, tidak masalah apakah Anda menyertakannya atau tidak. Memasukkannya juga tidak masalah:
```js {9}
function VideoPlayer({ src, isPlaying }) {
@@ -455,17 +457,17 @@ function VideoPlayer({ src, isPlaying }) {
}, [isPlaying, ref]);
```
-The [`set` functions](/reference/react/useState#setstate) returned by `useState` also have stable identity, so you will often see them omitted from the dependencies too. If the linter lets you omit a dependency without errors, it is safe to do.
+[Fungsi `set`](/reference/react/useState#setstate) yang dikembalikan oleh `useState` juga memiliki identitas yang stabil, sehingga Anda akan sering melihat fungsi ini dihilangkan dari dependensi. Jika *linter* mengizinkan Anda menghilangkan sebuah dependensi tanpa kesalahan, maka hal ini aman untuk dilakukan.
-Omitting always-stable dependencies only works when the linter can "see" that the object is stable. For example, if `ref` was passed from a parent component, you would have to specify it in the dependency array. However, this is good because you can't know whether the parent component always passes the same ref, or passes one of several refs conditionally. So your Effect _would_ depend on which ref is passed.
+Menghilangkan dependensi yang selalu stabil hanya berfungsi ketika linter dapat "melihat" bahwa objek tersebut stabil. Sebagai contoh, jika `ref` dioper dari komponen induk, Anda harus menspesifikasikannya dalam senarai dependensi. However, this is good because you can't know whether the parent component always passes the same ref, or passes one of several refs conditionally. So your Effect _would_ depend on which ref is passed.
-### Step 3: Add cleanup if needed {/*step-3-add-cleanup-if-needed*/}
+### Langkah 3: Tambahkan pembersihan jika diperlukan {/*step-3-add-cleanup-if-needed*/}
-Consider a different example. You're writing a `ChatRoom` component that needs to connect to the chat server when it appears. You are given a `createConnection()` API that returns an object with `connect()` and `disconnect()` methods. How do you keep the component connected while it is displayed to the user?
+Bayangkan contoh yang berbeda. Anda sedang menulis komponen `ChatRoom` yang perlu terhubung ke server obrolan ketika ditampilkan. Anda diberi API `createConnection()` yang mengembalikan sebuah objek dengan metode `connect()` dan `disconnect()`. Bagaimana Anda menjaga komponen tetap terhubung saat ditampilkan kepada pengguna?
-Start by writing the Effect logic:
+Mulai dengan menulis logika *Effect*:
```js
useEffect(() => {
@@ -474,7 +476,7 @@ useEffect(() => {
});
```
-It would be slow to connect to the chat after every re-render, so you add the dependency array:
+Akan sangat lambat untuk melakukan koneksi ke obrolan setelah setiap *render* ulang, jadi Anda menambahkan larik dependensi:
```js {4}
useEffect(() => {
@@ -483,9 +485,9 @@ useEffect(() => {
}, []);
```
-**The code inside the Effect does not use any props or state, so your dependency array is `[]` (empty). This tells React to only run this code when the component "mounts", i.e. appears on the screen for the first time.**
+**Kode di dalam *Effect* tidak menggunakan *props* atau *state* apapun, sehingga larik dependensi Anda adalah `[]` (kosong). Ini memberitahu React untuk hanya menjalankan kode ini ketika komponen "dipasang", yaitu muncul di layar untuk pertama kalinya.**
-Let's try running this code:
+Mari kita coba menjalankan kode ini:
@@ -498,19 +500,19 @@ export default function ChatRoom() {
const connection = createConnection();
connection.connect();
}, []);
- return Welcome to the chat! ;
+ return Selamat datang di ruang obrolan! ;
}
```
```js src/chat.js
export function createConnection() {
- // A real implementation would actually connect to the server
+ // Implementasi nyata akan benar-benar terhubung ke server
return {
connect() {
- console.log('✅ Connecting...');
+ console.log('✅ Menghubungkan...');
},
disconnect() {
- console.log('❌ Disconnected.');
+ console.log('❌ Terputus.');
}
};
}
@@ -522,15 +524,15 @@ input { display: block; margin-bottom: 20px; }
-This Effect only runs on mount, so you might expect `"✅ Connecting..."` to be printed once in the console. **However, if you check the console, `"✅ Connecting..."` gets printed twice. Why does it happen?**
+*Effect* ini hanya berjalan pada pemasangan, jadi Anda mungkin mengharapkan `"✅ Menghubungkan..."` dicetak sekali di konsol. **Namun, jika Anda memeriksa konsol, `"✅ Menghubungkan..."` akan dicetak dua kali. Mengapa hal ini bisa terjadi?**
-Imagine the `ChatRoom` component is a part of a larger app with many different screens. The user starts their journey on the `ChatRoom` page. The component mounts and calls `connection.connect()`. Then imagine the user navigates to another screen--for example, to the Settings page. The `ChatRoom` component unmounts. Finally, the user clicks Back and `ChatRoom` mounts again. This would set up a second connection--but the first connection was never destroyed! As the user navigates across the app, the connections would keep piling up.
+Bayangkan komponen `ChatRoom` merupakan bagian dari aplikasi yang lebih besar dengan banyak layar yang berbeda. Pengguna memulai perjalanan mereka di halaman `ChatRoom`. Komponen dipasang dan memanggil `connection.connect()`. Kemudian bayangkan pengguna menavigasi ke layar lain--misalnya, ke halaman Pengaturan. Akhirnya, pengguna mengklik Kembali dan `ChatRoom` terpasang kembali. Hal ini akan membuat sambungan kedua--tetapi sambungan pertama tidak pernah diputuskan! Ketika pengguna menavigasi aplikasi, koneksi akan terus menumpuk.
-Bugs like this are easy to miss without extensive manual testing. To help you spot them quickly, in development React remounts every component once immediately after its initial mount.
+Bug seperti ini mudah terlewatkan tanpa pengujian manual yang ekstensif. Untuk membantu Anda menemukannya dengan cepat, dalam pengembangan, React melakukan pemasangan ulang setiap komponen satu kali setelah pemasangan awal.
-Seeing the `"✅ Connecting..."` log twice helps you notice the real issue: your code doesn't close the connection when the component unmounts.
+Melihat log `"✅ Menghubungkan..."` dua kali akan membantu Anda mengetahui masalah yang sebenarnya: kode Anda tidak menutup koneksi ketika komponen dilepas.
-To fix the issue, return a *cleanup function* from your Effect:
+Untuk memperbaiki masalah ini, kembalikan *fungsi cleanup* dari Effect Anda:
```js {4-6}
useEffect(() => {
@@ -542,7 +544,7 @@ To fix the issue, return a *cleanup function* from your Effect:
}, []);
```
-React will call your cleanup function each time before the Effect runs again, and one final time when the component unmounts (gets removed). Let's see what happens when the cleanup function is implemented:
+React akan memanggil fungsi pembersihan Anda setiap kali sebelum *Effect* dijalankan kembali, dan satu kali lagi ketika komponen dilepas (dihapus). Mari kita lihat apa yang terjadi ketika fungsi pembersihan diimplementasikan:
@@ -556,19 +558,19 @@ export default function ChatRoom() {
connection.connect();
return () => connection.disconnect();
}, []);
- return Welcome to the chat! ;
+ return Selamat datang di ruang obrolan! ;
}
```
```js src/chat.js
export function createConnection() {
- // A real implementation would actually connect to the server
+ // Implementasi nyata akan benar-benar terhubung ke server
return {
connect() {
- console.log('✅ Connecting...');
+ console.log('✅ Menghubungkan...');
},
disconnect() {
- console.log('❌ Disconnected.');
+ console.log('❌ Terputus.');
}
};
}
@@ -580,27 +582,27 @@ input { display: block; margin-bottom: 20px; }
-Now you get three console logs in development:
+Sekarang Anda mendapatkan tiga log konsol dalam pengembangan:
-1. `"✅ Connecting..."`
-2. `"❌ Disconnected."`
-3. `"✅ Connecting..."`
+1. `"✅ Menghubungkan..."`
+2. `"❌ Terputus."`
+3. `"✅ Menghubungkan..."`
-**This is the correct behavior in development.** By remounting your component, React verifies that navigating away and back would not break your code. Disconnecting and then connecting again is exactly what should happen! When you implement the cleanup well, there should be no user-visible difference between running the Effect once vs running it, cleaning it up, and running it again. There's an extra connect/disconnect call pair because React is probing your code for bugs in development. This is normal--don't try to make it go away!
+**Ini adalah perilaku yang benar dalam pengembangan.** Dengan memasang kembali komponen Anda, React memverifikasi bahwa navigasi menjauh dan kembali tidak akan merusak kode Anda. Memutuskan sambungan dan kemudian menyambungkannya kembali adalah hal yang seharusnya terjadi! Ketika Anda mengimplementasikan pembersihan dengan baik, seharusnya tidak ada perbedaan yang terlihat oleh pengguna antara menjalankan *Effect* sekali vs menjalankannya, membersihkannya, dan menjalankannya lagi. Ada pasangan panggilan tambahan untuk menghubungkan/memutuskan koneksi karena React sedang menyelidiki kode Anda untuk mencari bug dalam pengembangan. Ini adalah hal yang normal--jangan mencoba untuk menghilangkannya!
-**In production, you would only see `"✅ Connecting..."` printed once.** Remounting components only happens in development to help you find Effects that need cleanup. You can turn off [Strict Mode](/reference/react/StrictMode) to opt out of the development behavior, but we recommend keeping it on. This lets you find many bugs like the one above.
+**Dalam produksi, Anda hanya akan melihat `"✅ Menghubungkan..."` dicetak satu kali.** Memasang kembali komponen hanya terjadi dalam pengembangan untuk membantu Anda menemukan Efek yang perlu dibersihkan. Anda dapat mematikan [Strict Mode](/reference/react/StrictMode) untuk keluar dari perilaku pengembangan, tetapi kami sarankan untuk tetap mengaktifkannya. Hal ini memungkinkan Anda menemukan banyak bug seperti di atas.
-## How to handle the Effect firing twice in development? {/*how-to-handle-the-effect-firing-twice-in-development*/}
+## Bagaimana cara menangani *Effect* yang ditembakkan dua kali dalam pengembangan? {/*how-to-handle-the-effect-firing-twice-in-development*/}
-React intentionally remounts your components in development to find bugs like in the last example. **The right question isn't "how to run an Effect once", but "how to fix my Effect so that it works after remounting".**
+React secara sengaja memasang ulang komponen Anda dalam pengembangan untuk menemukan bug seperti pada contoh terakhir. **Pertanyaan yang tepat bukanlah "bagaimana cara menjalankan sebuah *Effect* sekali saja", tetapi "bagaimana cara memperbaiki *Effect* saya agar dapat berfungsi setelah dipasang ulang".**
-Usually, the answer is to implement the cleanup function. The cleanup function should stop or undo whatever the Effect was doing. The rule of thumb is that the user shouldn't be able to distinguish between the Effect running once (as in production) and a _setup → cleanup → setup_ sequence (as you'd see in development).
+Biasanya, jawabannya adalah menerapkan fungsi pembersihan. Fungsi pembersihan harus menghentikan atau membatalkan apa pun yang sedang dilakukan oleh Effect. Aturan praktisnya adalah bahwa pengguna seharusnya tidak dapat membedakan antara Effect yang berjalan sekali (seperti dalam produksi) dan urutan _setup → cleanup → setup_ (seperti yang Anda lihat dalam pengembangan).
-Most of the Effects you'll write will fit into one of the common patterns below.
+Sebagian besar *Effect* yang akan Anda tulis, akan sesuai dengan salah satu pola umum di bawah ini.
-### Controlling non-React widgets {/*controlling-non-react-widgets*/}
+### Mengontrol *widget* di luar React {/*controlling-non-react-widgets*/}
-Sometimes you need to add UI widgets that aren't written to React. For example, let's say you're adding a map component to your page. It has a `setZoomLevel()` method, and you'd like to keep the zoom level in sync with a `zoomLevel` state variable in your React code. Your Effect would look similar to this:
+Terkadang Anda perlu menambahkan *widget* UI yang tidak ditulis untuk React. Sebagai contoh, katakanlah Anda menambahkan komponen peta ke halaman Anda. Komponen ini memiliki metode `setZoomLevel()`, dan Anda ingin menjaga tingkat *zoom* tetap sinkron dengan variabel *state* `zoomLevel` dalam kode React Anda. *Effect* Anda akan terlihat seperti ini:
```js
useEffect(() => {
@@ -609,9 +611,9 @@ useEffect(() => {
}, [zoomLevel]);
```
-Note that there is no cleanup needed in this case. In development, React will call the Effect twice, but this is not a problem because calling `setZoomLevel` twice with the same value does not do anything. It may be slightly slower, but this doesn't matter because it won't remount needlessly in production.
+Perhatikan bahwa tidak ada pembersihan yang diperlukan dalam kasus ini. Dalam pengembangan, React akan memanggil Effect dua kali, tetapi ini tidak menjadi masalah karena memanggil `setZoomLevel` dua kali dengan nilai yang sama tidak akan melakukan apa-apa. Ini mungkin sedikit lebih lambat, tetapi ini tidak menjadi masalah karena tidak akan melakukan pemanggilan ulang yang tidak perlu dalam produksi.
-Some APIs may not allow you to call them twice in a row. For example, the [`showModal`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) method of the built-in [``](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement) element throws if you call it twice. Implement the cleanup function and make it close the dialog:
+Beberapa API mungkin tidak mengizinkan Anda memanggilnya dua kali berturut-turut. Misalnya, metode [`showModal`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) dari elemen [``](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement) bawaan akan melempar *error* jika Anda memanggilnya dua kali. Implementasi fungsi pembersihan untuk membuatnya menutup dialog:
```js {4}
useEffect(() => {
@@ -621,11 +623,11 @@ useEffect(() => {
}, []);
```
-In development, your Effect will call `showModal()`, then immediately `close()`, and then `showModal()` again. This has the same user-visible behavior as calling `showModal()` once, as you would see in production.
+Dalam pengembangan, *Effect* Anda akan memanggil `showModal()`, lalu segera `close()`, dan kemudian `showModal()` lagi. Ini memiliki perilaku yang terlihat oleh pengguna yang sama dengan memanggil `showModal()` satu kali, seperti yang akan Anda lihat dalam produksi.
-### Subscribing to events {/*subscribing-to-events*/}
+### Berlangganan *events* {/*subscribing-to-events*/}
-If your Effect subscribes to something, the cleanup function should unsubscribe:
+Jika Efek Anda berlangganan sesuatu, fungsi pembersihan harus menghentikan langganan:
```js {6}
useEffect(() => {
@@ -637,27 +639,27 @@ useEffect(() => {
}, []);
```
-In development, your Effect will call `addEventListener()`, then immediately `removeEventListener()`, and then `addEventListener()` again with the same handler. So there would be only one active subscription at a time. This has the same user-visible behavior as calling `addEventListener()` once, as in production.
+Dalam mode pengembangan, *Effect* Anda akan memanggil `addEventListener()`, lalu segera `hapusEventListener()`, dan kemudian `addEventListener()` lagi dengan *event handler* yang sama. Jadi hanya akan ada satu langganan yang aktif pada satu waktu. Ini memiliki perilaku yang terlihat oleh pengguna yang sama dengan memanggil `addEventListener()` sekali, seperti dalam produksi.
-### Triggering animations {/*triggering-animations*/}
+### Memicu animasi {/*triggering-animations*/}
-If your Effect animates something in, the cleanup function should reset the animation to the initial values:
+Jika *Effect* Anda menganimasikan sesuatu, fungsi pembersihan harus mengatur ulang animasi ke nilai awal:
```js {4-6}
useEffect(() => {
const node = ref.current;
- node.style.opacity = 1; // Trigger the animation
+ node.style.opacity = 1; // Picu animasi
return () => {
- node.style.opacity = 0; // Reset to the initial value
+ node.style.opacity = 0; // Set ulang ke nilai awal
};
}, []);
```
-In development, opacity will be set to `1`, then to `0`, and then to `1` again. This should have the same user-visible behavior as setting it to `1` directly, which is what would happen in production. If you use a third-party animation library with support for tweening, your cleanup function should reset the timeline to its initial state.
+Dalam mode pengembangan, *opacity* akan diatur ke `1`, kemudian ke `0`, dan kemudian ke `1` lagi. Ini seharusnya memiliki perilaku yang terlihat oleh pengguna yang sama dengan pengaturan ke `1` secara langsung, yang akan terjadi dalam produksi. Jika Anda menggunakan pustaka animasi pihak ketiga yang mendukung *tweening*, fungsi pembersihan Anda akan mengatur ulang *timeline* ke kondisi awal.
-### Fetching data {/*fetching-data*/}
+### Mengambil data {/*fetching-data*/}
-If your Effect fetches something, the cleanup function should either [abort the fetch](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) or ignore its result:
+Jika Efek Anda mengambil sesuatu, fungsi pembersihan harus [membatalkan pengambilan](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) atau mengabaikan hasilnya:
```js {2,6,13-15}
useEffect(() => {
@@ -678,11 +680,11 @@ useEffect(() => {
}, [userId]);
```
-You can't "undo" a network request that already happened, but your cleanup function should ensure that the fetch that's _not relevant anymore_ does not keep affecting your application. If the `userId` changes from `'Alice'` to `'Bob'`, cleanup ensures that the `'Alice'` response is ignored even if it arrives after `'Bob'`.
+Anda tidak dapat "membatalkan" *network request* yang telah terjadi, tetapi fungsi pembersihan Anda harus memastikan bahwa pengambilan data yang _tidak relevan lagi_ tidak terus mempengaruhi aplikasi Anda. Jika `userId` berubah dari `'Alice'` menjadi `'Bob'`, pembersihan memastikan bahwa respons `'Alice'` diabaikan meskipun ia datang setelah `'Bob'`.
-**In development, you will see two fetches in the Network tab.** There is nothing wrong with that. With the approach above, the first Effect will immediately get cleaned up so its copy of the `ignore` variable will be set to `true`. So even though there is an extra request, it won't affect the state thanks to the `if (!ignore)` check.
+**Dalam mode pengembangan, Anda akan melihat dua *fetch* di tab *Network*.** Tidak ada yang salah dengan hal itu. Dengan pendekatan di atas, *Effect* pertama akan segera dibersihkan sehingga salinan variabel `ignore` akan disetel ke `true`. Jadi, meskipun ada *request* tambahan, hal itu tidak akan mempengaruhi *state* berkat pemeriksaan `if (!ignore)`.
-**In production, there will only be one request.** If the second request in development is bothering you, the best approach is to use a solution that deduplicates requests and caches their responses between components:
+**Dalam mode produksi, hanya akan ada satu *request*.** Jika *request* kedua dalam pengembangan mengganggu Anda, pendekatan terbaik adalah menggunakan solusi yang menduplikasi *request* dan menyimpan responsnya di antara komponen:
```js
function TodoList() {
@@ -690,31 +692,31 @@ function TodoList() {
// ...
```
-This will not only improve the development experience, but also make your application feel faster. For example, the user pressing the Back button won't have to wait for some data to load again because it will be cached. You can either build such a cache yourself or use one of the many alternatives to manual fetching in Effects.
+Hal ini tidak hanya akan meningkatkan pengalaman pengembangan, tetapi juga membuat aplikasi Anda terasa lebih cepat. Sebagai contoh, pengguna yang menekan tombol Kembali tidak perlu menunggu data dimuat lagi, karena data tersebut akan di-*cache*. Anda bisa membuat *cache* sendiri atau menggunakan salah satu dari banyak alternatif untuk *fetching* secara manual di *Effects*.
-#### What are good alternatives to data fetching in Effects? {/*what-are-good-alternatives-to-data-fetching-in-effects*/}
+#### Apa saja alternatif yang bagus untuk pengambilan data di *Effects*? {/*what-are-good-alternatives-to-data-fetching-in-effects*/}
-Writing `fetch` calls inside Effects is a [popular way to fetch data](https://www.robinwieruch.de/react-hooks-fetch-data/), especially in fully client-side apps. This is, however, a very manual approach and it has significant downsides:
+Menulis panggilan `fetch` di dalam *Effects* adalah [cara populer untuk mengambil data](https://www.robinwieruch.de/react-hooks-fetch-data/), terutama di aplikasi yang sepenuhnya berbasis klien. Namun, ini adalah pendekatan yang sangat manual dan memiliki kelemahan yang signifikan:
-- **Effects don't run on the server.** This means that the initial server-rendered HTML will only include a loading state with no data. The client computer will have to download all JavaScript and render your app only to discover that now it needs to load the data. This is not very efficient.
-- **Fetching directly in Effects makes it easy to create "network waterfalls".** You render the parent component, it fetches some data, renders the child components, and then they start fetching their data. If the network is not very fast, this is significantly slower than fetching all data in parallel.
-- **Fetching directly in Effects usually means you don't preload or cache data.** For example, if the component unmounts and then mounts again, it would have to fetch the data again.
-- **It's not very ergonomic.** There's quite a bit of boilerplate code involved when writing `fetch` calls in a way that doesn't suffer from bugs like [race conditions.](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect)
+- ***Effects* tidak berjalan di server.** Ini berarti bahwa HTML awal yang di-*render* di server hanya akan menyertakan status pemuatan tanpa data. Komputer klien harus mengunduh semua JavaScript dan me-*render* aplikasi Anda hanya untuk mengetahui bahwa sekarang ia perlu memuat data. Hal ini sangat tidak efisien.
+- **Mengambil data secara langsung dalam *Effects* memudahkan untuk menciptakan "air terjun (*waterfall*) jaringan".** Anda me-*render* komponen induk, mengambil beberapa data, me-*render* komponen anak, dan kemudian komponen anak mulai mengambil datanya. Jika jaringan tidak terlalu cepat, hal ini jauh lebih lambat daripada mengambil semua data secara paralel.
+- **Mengambil data secara langsung dalam *Effects* biasanya berarti Anda tidak melakukan pramuat atau *cache* data.** Sebagai contoh, jika komponen dilepas dan kemudian dipasang lagi, komponen tersebut harus mengambil data lagi.
+- **Sangat tidak ergonomis.** Ada cukup banyak kode *boilerplate* yang terlibat ketika menulis panggilan `fetch` dengan cara yang tidak mengalami bug seperti [*race condition*.](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect)
-This list of downsides is not specific to React. It applies to fetching data on mount with any library. Like with routing, data fetching is not trivial to do well, so we recommend the following approaches:
+Daftar kelemahan ini tidak spesifik untuk React. Ini berlaku untuk mengambil data saat pemasangan komponen dengan pustaka apa pun. Seperti halnya dengan *routing*, pengambilan data bukanlah hal yang sepele untuk dilakukan dengan baik, jadi kami merekomendasikan pendekatan berikut ini:
-- **If you use a [framework](/learn/start-a-new-react-project#production-grade-react-frameworks), use its built-in data fetching mechanism.** Modern React frameworks have integrated data fetching mechanisms that are efficient and don't suffer from the above pitfalls.
-- **Otherwise, consider using or building a client-side cache.** Popular open source solutions include [React Query](https://tanstack.com/query/latest), [useSWR](https://swr.vercel.app/), and [React Router 6.4+.](https://beta.reactrouter.com/en/main/start/overview) You can build your own solution too, in which case you would use Effects under the hood, but add logic for deduplicating requests, caching responses, and avoiding network waterfalls (by preloading data or hoisting data requirements to routes).
+- **Jika Anda menggunakan [kerangka kerja (*framework*)](/learn/start-a-new-react-project#production-grade-react-frameworks), gunakan mekanisme pengambilan data yang sudah ada di dalamnya.** Kerangka kerja React modern memiliki mekanisme pengambilan data terintegrasi yang efisien dan tidak mengalami kendala di atas.
+- **Jika tidak, pertimbangkan untuk menggunakan atau membangun *cache* sisi klien.** Solusi sumber terbuka (*open source*) yang populer termasuk [React Query](https://tanstack.com/query/latest), [useSWR](https://swr.vercel.app/), dan [React Router 6.4+.](https://beta.reactrouter.com/en/main/start/overview) Anda juga dapat membuat solusi sendiri, dalam hal ini Anda dapat menggunakan *Effects* di dalamnya, tetapi menambahkan logika untuk menduplikasi *request*, menyimpan respons dalam *cache*, dan menghindari *waterfall* jaringan (dengan melakukan pramuat data atau mengangkat kebutuhan data ke *route*).
-You can continue fetching data directly in Effects if neither of these approaches suit you.
+Anda dapat terus mengambil data secara langsung di *Effects* jika tidak ada satu pun dari pendekatan ini yang cocok untuk Anda.
-### Sending analytics {/*sending-analytics*/}
+### Mengirim analitik {/*sending-analytics*/}
-Consider this code that sends an analytics event on the page visit:
+Perhatikan kode berikut ini yang mengirimkan *event* analitik pada kunjungan halaman:
```js
useEffect(() => {
@@ -722,18 +724,18 @@ useEffect(() => {
}, [url]);
```
-In development, `logVisit` will be called twice for every URL, so you might be tempted to try to fix that. **We recommend keeping this code as is.** Like with earlier examples, there is no *user-visible* behavior difference between running it once and running it twice. From a practical point of view, `logVisit` should not do anything in development because you don't want the logs from the development machines to skew the production metrics. Your component remounts every time you save its file, so it logs extra visits in development anyway.
+Dalam mode pengembangan, `logVisit` akan dipanggil dua kali untuk setiap URL, sehingga Anda mungkin tergoda untuk mencoba memperbaikinya. **Kami sarankan untuk membiarkan kode ini apa adanya.** Seperti contoh-contoh sebelumnya, tidak ada perbedaan perilaku yang *dilihat oleh pengguna* antara menjalankannya sekali dan menjalankannya dua kali. Dari sudut pandang praktis, `logVisit` tidak boleh melakukan apa pun dalam pengembangan karena Anda tidak ingin log dari mesin pengembangan mempengaruhi metrik produksi. Komponen Anda akan dimuat ulang setiap kali Anda menyimpan berkasnyanya, sehingga komponen tersebut tetap mencatat kunjungan ekstra dalam pengembangan.
-**In production, there will be no duplicate visit logs.**
+**Dalam mode produksi, tidak akan ada log kunjungan yang terduplikasi.**
-To debug the analytics events you're sending, you can deploy your app to a staging environment (which runs in production mode) or temporarily opt out of [Strict Mode](/reference/react/StrictMode) and its development-only remounting checks. You may also send analytics from the route change event handlers instead of Effects. For more precise analytics, [intersection observers](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) can help track which components are in the viewport and how long they remain visible.
+Untuk men-debug *event* analitik yang Anda kirimkan, Anda bisa men-*deploy* aplikasi Anda ke lingkungan *staging* (yang berjalan dalam mode produksi) atau untuk sementara tidak menggunakan [Strict Mode](/reference/react/StrictMode) dan pengecekan ulang khusus pengembangannya. Anda juga dapat mengirimkan analitik dari *event handler* perubahan *route*, bukan dari *Effects*. Untuk analitik yang lebih tepat, [*intersection observers*](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) dapat membantu melacak komponen mana yang ada di tampilan layar dan berapa lama komponen tersebut tetap terlihat.
-### Not an Effect: Initializing the application {/*not-an-effect-initializing-the-application*/}
+### Bukan *Effect*: Menginisialisasi aplikasi {/*not-an-effect-initializing-the-application*/}
-Some logic should only run once when the application starts. You can put it outside your components:
+Beberapa logika seharusnya hanya berjalan sekali ketika aplikasi dijalankan. Anda dapat meletakkannya di luar komponen Anda:
```js {2-3}
-if (typeof window !== 'undefined') { // Check if we're running in the browser.
+if (typeof window !== 'undefined') { // Periksa apakah kita berjalan di browser.
checkAuthToken();
loadDataFromLocalStorage();
}
@@ -743,37 +745,37 @@ function App() {
}
```
-This guarantees that such logic only runs once after the browser loads the page.
+Hal ini menjamin bahwa logika tersebut hanya berjalan satu kali setelah browser memuat halaman.
-### Not an Effect: Buying a product {/*not-an-effect-buying-a-product*/}
+### Bukan *Effect*: Membeli produk {/*not-an-effect-buying-a-product*/}
-Sometimes, even if you write a cleanup function, there's no way to prevent user-visible consequences of running the Effect twice. For example, maybe your Effect sends a POST request like buying a product:
+Terkadang, meskipun Anda menulis fungsi pembersihan, tidak ada cara untuk mencegah konsekuensi yang terlihat oleh pengguna dari menjalankan *Effect* dua kali. Misalnya, mungkin *Effect* Anda mengirimkan *request* POST seperti membeli produk:
```js {2-3}
useEffect(() => {
- // 🔴 Wrong: This Effect fires twice in development, exposing a problem in the code.
+ // 🔴 Salah: Effect ini ditembakkan dua kali di pengembangan, mengungkapkan masalah dalam kode.
fetch('/api/buy', { method: 'POST' });
}, []);
```
-You wouldn't want to buy the product twice. However, this is also why you shouldn't put this logic in an Effect. What if the user goes to another page and then presses Back? Your Effect would run again. You don't want to buy the product when the user *visits* a page; you want to buy it when the user *clicks* the Buy button.
+Anda tidak ingin membeli produk dua kali. Namun, ini juga alasan mengapa Anda tidak boleh meletakkan logika ini di dalam sebuah *Effect*. Bagaimana jika pengguna pergi ke halaman lain dan kemudian menekan Kembali? *Effect* Anda akan berjalan lagi. Anda tidak ingin membeli produk ketika pengguna *mengunjungi* halaman; Anda ingin membelinya ketika pengguna *mengklik* tombol Beli.
-Buying is not caused by rendering; it's caused by a specific interaction. It should run only when the user presses the button. **Delete the Effect and move your `/api/buy` request into the Buy button event handler:**
+Pembelian tidak disebabkan oleh rendering; ini disebabkan oleh interaksi tertentu. Interaksi ini harus berjalan hanya ketika pengguna menekan tombol. **Hapus *Effect* dan pindahkan *request* `/api/buy` Anda ke dalam *event handler* tombol Beli:**
```js {2-3}
function handleClick() {
- // ✅ Buying is an event because it is caused by a particular interaction.
+ // ✅ Pembelian adalah sebuah event karena disebabkan oleh interaksi tertentu.
fetch('/api/buy', { method: 'POST' });
}
```
-**This illustrates that if remounting breaks the logic of your application, this usually uncovers existing bugs.** From a user's perspective, visiting a page shouldn't be different from visiting it, clicking a link, then pressing Back to view the page again. React verifies that your components abide by this principle by remounting them once in development.
+**Hal ini mengilustrasikan bahwa jika pemasangan ulang merusak logika aplikasi Anda, hal ini biasanya akan menemukan bug yang ada.** Dari sudut pandang pengguna, mengunjungi sebuah halaman seharusnya tidak berbeda dengan mengunjunginya, mengklik sebuah tautan, lalu menekan Kembali untuk melihat halaman tersebut kembali. React memverifikasi bahwa komponen Anda mematuhi prinsip ini dengan memasang ulang komponen tersebut sekali dalam pengembangan.
-## Putting it all together {/*putting-it-all-together*/}
+## Menyatukan semuanya {/*putting-it-all-together*/}
-This playground can help you "get a feel" for how Effects work in practice.
+*Playground* ini dapat membantu Anda "merasakan" bagaimana *Effect* bekerja dalam praktiknya.
-This example uses [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) to schedule a console log with the input text to appear three seconds after the Effect runs. The cleanup function cancels the pending timeout. Start by pressing "Mount the component":
+Contoh ini menggunakan [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) untuk menjadwalkan log konsol dengan teks input untuk muncul tiga detik setelah *Effect* berjalan. Fungsi pembersihan akan membatalkan batas waktu yang tertunda. Mulailah dengan menekan "Pasang komponen":
@@ -788,11 +790,11 @@ function Playground() {
console.log('⏰ ' + text);
}
- console.log('🔵 Schedule "' + text + '" log');
+ console.log('🔵 Menjadwalkan log "' + text + '"');
const timeoutId = setTimeout(onTimeout, 3000);
return () => {
- console.log('🟡 Cancel "' + text + '" log');
+ console.log('🟡 Membatalkan log "' + text + '"');
clearTimeout(timeoutId);
};
}, [text]);
@@ -800,7 +802,7 @@ function Playground() {
return (
<>
- What to log:{' '}
+ Yang ingin di-log:{' '}
setText(e.target.value)}
@@ -816,7 +818,7 @@ export default function App() {
return (
<>
setShow(!show)}>
- {show ? 'Unmount' : 'Mount'} the component
+ {show ? 'Lepas' : 'Pasang'} komponen
{show && }
{show && }
@@ -827,21 +829,21 @@ export default function App() {
-You will see three logs at first: `Schedule "a" log`, `Cancel "a" log`, and `Schedule "a" log` again. Three second later there will also be a log saying `a`. As you learned earlier, the extra schedule/cancel pair is because React remounts the component once in development to verify that you've implemented cleanup well.
+Anda akan melihat tiga log pada awalnya: `Menjadwalkan log "a"`, `Membatalkan log "a"`, dan `Menjadwalkan log "a"` lagi. Three second later there will also be a log saying `a`. Tiga detik kemudian juga akan ada log yang bertuliskan `a`. Seperti yang Anda pelajari sebelumnya, pasangan penjadwalan/pembatalan tambahan adalah karena React me-remount komponen sekali dalam pengembangan untuk memverifikasi bahwa Anda telah mengimplementasikan pembersihan dengan baik.
-Now edit the input to say `abc`. If you do it fast enough, you'll see `Schedule "ab" log` immediately followed by `Cancel "ab" log` and `Schedule "abc" log`. **React always cleans up the previous render's Effect before the next render's Effect.** This is why even if you type into the input fast, there is at most one timeout scheduled at a time. Edit the input a few times and watch the console to get a feel for how Effects get cleaned up.
+Sekarang edit input menjadi `abc`. Jika Anda melakukannya dengan cukup cepat, you'll see `Menjadwalkan log "ab"` segera diikuti dengan `Membatalkan log "ab"` dan `Menjadwalkan log "abc"`. **React selalu membersihkan *Effect* dari render sebelumnya sebelum *Effect* dari render berikutnya.** Inilah sebabnya mengapa meskipun Anda mengetikkan input dengan cepat, hanya ada satu *timeout* yang dijadwalkan dalam satu waktu. Edit input beberapa kali dan lihat konsol untuk mengetahui bagaimana *Effect* dibersihkan.
-Type something into the input and then immediately press "Unmount the component". Notice how unmounting cleans up the last render's Effect. Here, it clears the last timeout before it has a chance to fire.
+Ketik sesuatu ke dalam input, lalu segera tekan "Lepas komponen". Perhatikan bagaimana melepas komponen membersihkan *Effect* render terakhir. Di sini, *Effect* membersihkan *timeout* terakhir sebelum sempat menembak.
-Finally, edit the component above and comment out the cleanup function so that the timeouts don't get cancelled. Try typing `abcde` fast. What do you expect to happen in three seconds? Will `console.log(text)` inside the timeout print the *latest* `text` and produce five `abcde` logs? Give it a try to check your intuition!
+Terakhir, edit komponen di atas dan beri komentar pada fungsi pembersihan agar *timeout* tidak dibatalkan. Coba ketik `abcde` dengan cepat. Apa yang Anda kira akan terjadi dalam tiga detik? Akankah `console.log(text)` di dalam *timeout* mencetak `text` *terbaru* dan menghasilkan lima log `abcde`? Cobalah untuk menguji intuisi Anda!
-Three seconds later, you should see a sequence of logs (`a`, `ab`, `abc`, `abcd`, and `abcde`) rather than five `abcde` logs. **Each Effect "captures" the `text` value from its corresponding render.** It doesn't matter that the `text` state changed: an Effect from the render with `text = 'ab'` will always see `'ab'`. In other words, Effects from each render are isolated from each other. If you're curious how this works, you can read about [closures](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures).
+Tiga detik kemudian, Anda akan melihat urutan log (`a`, `ab`, `abc`, `abcd`, and `abcde`) bukan lima log `abcde`. **Setiap Efek "menangkap" nilai `teks` dari render yang sesuai.** Tidak masalah jika *state* `text` berubah: Efek dari render dengan `text = 'ab'` akan selalu menjadi `'ab'`. Jika Anda penasaran bagaimana cara kerjanya, Anda dapat membaca tentang [*closures*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures).
-#### Each render has its own Effects {/*each-render-has-its-own-effects*/}
+#### Setiap render memiliki *Effect* tersendiri {/*each-render-has-its-own-effects*/}
-You can think of `useEffect` as "attaching" a piece of behavior to the render output. Consider this Effect:
+Anda dapat menganggap `useEffect` sebagai "melampirkan" sebuah perilaku ke keluaran *render*. Perhatikan *Effect* berikut:
```js
export default function ChatRoom({ roomId }) {
@@ -851,123 +853,123 @@ export default function ChatRoom({ roomId }) {
return () => connection.disconnect();
}, [roomId]);
- return Welcome to {roomId}! ;
+ return Selamat datang di {roomId}! ;
}
```
-Let's see what exactly happens as the user navigates around the app.
+Mari kita lihat apa yang sebenarnya terjadi saat pengguna menavigasi aplikasi ini.
-#### Initial render {/*initial-render*/}
+#### *Render* awal {/*initial-render*/}
-The user visits ` `. Let's [mentally substitute](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `roomId` with `'general'`:
+Pengguna mengunjungi ` `. Mari kita [menukar secara mental](/learn/state-as-a-snapshot#rendering-takes-a-snapshot-in-time) `roomId` dengan `'general'`:
```js
- // JSX for the first render (roomId = "general")
- return Welcome to general! ;
+ // JSX untuk render pertama (roomId = "general")
+ return Selamat datang di general! ;
```
-**The Effect is *also* a part of the rendering output.** The first render's Effect becomes:
+***Effect* *juga* bagian dari keluaran *render*.** *Effect* render pertama adalah sebagai berikut:
```js
- // Effect for the first render (roomId = "general")
+ // Effect untuk render pertama (roomId = "general")
() => {
const connection = createConnection('general');
connection.connect();
return () => connection.disconnect();
},
- // Dependencies for the first render (roomId = "general")
+ // Dependensi untuk render pertama (roomId = "general")
['general']
```
-React runs this Effect, which connects to the `'general'` chat room.
+React menjalankan *Effect* ini, yang menghubungkan ke ruang obrolan `'general'`.
-#### Re-render with same dependencies {/*re-render-with-same-dependencies*/}
+#### *Render ulang* dengan dependensi yang sama {/*re-render-with-same-dependencies*/}
-Let's say ` ` re-renders. The JSX output is the same:
+Bayangkan ` ` di-*render* ulang. Keluaran JSX tetap sama:
```js
- // JSX for the second render (roomId = "general")
- return Welcome to general! ;
+ // JSX untuk render kedua (roomId = "general")
+ return Selamat datang di general! ;
```
-React sees that the rendering output has not changed, so it doesn't update the DOM.
+React melihat bahwa keluaran *render* tidak berubah, jadi ia tidak memperbarui DOM.
-The Effect from the second render looks like this:
+Effect dari *render* kedua menjadi seperti ini:
```js
- // Effect for the second render (roomId = "general")
+ // Effect untuk render kedua (roomId = "general")
() => {
const connection = createConnection('general');
connection.connect();
return () => connection.disconnect();
},
- // Dependencies for the second render (roomId = "general")
+ // Dependensi untuk render kedua (roomId = "general")
['general']
```
-React compares `['general']` from the second render with `['general']` from the first render. **Because all dependencies are the same, React *ignores* the Effect from the second render.** It never gets called.
+React membandingkan `['general']` dari *render* kedua dengan `['general']` dari *render* pertama. **Karena semua dependensi sama, React *mengabaikan* *Effect* dari render kedua.** Ia tidak pernah dipanggil.
-#### Re-render with different dependencies {/*re-render-with-different-dependencies*/}
+#### *Render ulang* dengan dependensi berbeda {/*re-render-with-different-dependencies*/}
-Then, the user visits ` `. This time, the component returns different JSX:
+Kemudian, pengguna mengunjungi ` `. Kali ini, komponen mengembalikan JSX yang berbeda:
```js
- // JSX for the third render (roomId = "travel")
- return Welcome to travel! ;
+ // JSX untuk render ketiga (roomId = "travel")
+ return Selamat datang di travel! ;
```
-React updates the DOM to change `"Welcome to general"` into `"Welcome to travel"`.
+React memperbarui DOM dengan mengubah `"Selamat datang di general"` menjadi `"Selamat datang di travel"`.
-The Effect from the third render looks like this:
+*Effect* dari *render* ketiga menjadi seperti ini:
```js
- // Effect for the third render (roomId = "travel")
+ // Effect untuk render ketiga (roomId = "travel")
() => {
const connection = createConnection('travel');
connection.connect();
return () => connection.disconnect();
},
- // Dependencies for the third render (roomId = "travel")
+ // Dependensi untuk render ketiga (roomId = "travel")
['travel']
```
-React compares `['travel']` from the third render with `['general']` from the second render. One dependency is different: `Object.is('travel', 'general')` is `false`. The Effect can't be skipped.
+React membandingkan `['travel']` dari *render* ketiga dengan `['general']` dari *render* kedua. Satu dependensi berbeda: `Object.is('travel', 'general')` adalah `false`. *Effect* tidak dapat dilewati.
-**Before React can apply the Effect from the third render, it needs to clean up the last Effect that _did_ run.** The second render's Effect was skipped, so React needs to clean up the first render's Effect. If you scroll up to the first render, you'll see that its cleanup calls `disconnect()` on the connection that was created with `createConnection('general')`. This disconnects the app from the `'general'` chat room.
+**Sebelum React dapat menerapkan *Effect* dari *render* ketiga, React perlu membersihkan *Effect* terakhir yang _dilewati_.** *Effect* pada *render* kedua dilewati, sehingga React perlu membersihkan *Effect* pada *render* pertama. Jika Anda menggulir ke atas ke *render* pertama, Anda akan melihat bahwa pembersihannya memanggil `disconnect()` pada koneksi yang dibuat dengan `createConnection('general')`. Ini akan memutuskan aplikasi dari ruang obrolan `'general'`.
-After that, React runs the third render's Effect. It connects to the `'travel'` chat room.
+Setelah itu, React menjalankan *Effect* render ketiga. Ia menghubungkan ke ruang obrolan `'travel'`.
-#### Unmount {/*unmount*/}
+#### Pelepasan {/*unmount*/}
-Finally, let's say the user navigates away, and the `ChatRoom` component unmounts. React runs the last Effect's cleanup function. The last Effect was from the third render. The third render's cleanup destroys the `createConnection('travel')` connection. So the app disconnects from the `'travel'` room.
+Terakhir, katakanlah pengguna melakukan navigasi, dan komponen `ChatRoom` dilepas. React menjalankan fungsi pembersihan *Effect* terakhir. Effect terakhir berasal dari *render* ketiga. Pembersihan *render* ketiga menghancurkan koneksi `createConnection('travel')`. Sehingga aplikasi terputus dari ruang `'travel'`.
-#### Development-only behaviors {/*development-only-behaviors*/}
+#### Perilaku mode pengembangan {/*development-only-behaviors*/}
-When [Strict Mode](/reference/react/StrictMode) is on, React remounts every component once after mount (state and DOM are preserved). This [helps you find Effects that need cleanup](#step-3-add-cleanup-if-needed) and exposes bugs like race conditions early. Additionally, React will remount the Effects whenever you save a file in development. Both of these behaviors are development-only.
+Ketika [*Strict Mode*](/reference/react/StrictMode) diaktifkan, React akan memasang ulang setiap komponen satu kali setelah pemasangan (*state* dan DOM akan dipertahankan). Hal ini [membantu Anda menemukan *Effect* yang membutuhkan pembersihan](#step-3-add-cleanup-if-needed) dan mengekspos bug seperti *race condition* lebih awal. Selain itu, React akan memasang ulang *Effect* setiap kali Anda menyimpan berkas dalam pengembangan. Kedua perilaku ini hanya untuk mode pengembangan.
-- Unlike events, Effects are caused by rendering itself rather than a particular interaction.
-- Effects let you synchronize a component with some external system (third-party API, network, etc).
-- By default, Effects run after every render (including the initial one).
-- React will skip the Effect if all of its dependencies have the same values as during the last render.
-- You can't "choose" your dependencies. They are determined by the code inside the Effect.
-- Empty dependency array (`[]`) corresponds to the component "mounting", i.e. being added to the screen.
-- In Strict Mode, React mounts components twice (in development only!) to stress-test your Effects.
-- If your Effect breaks because of remounting, you need to implement a cleanup function.
-- React will call your cleanup function before the Effect runs next time, and during the unmount.
+- Tidak seperti *events*, *Effects* disebabkan oleh pe-*render*-an itu sendiri, bukan oleh interaksi tertentu.
+- *Effects* memungkinkan Anda menyinkronkan komponen dengan suatu sistem eksternal (API pihak ketiga, jaringan, dll.).
+- Secara default, *Effects* dijalankan setelah setiap *render* (termasuk *render* awal).
+- React akan melewatkan *Effect* jika semua dependensinya memiliki nilai yang sama dengan nilai pada saat render terakhir.
+- Anda tidak dapat "memilih" dependensi Anda. Mereka ditentukan oleh kode di dalam *Effect*.
+- Larik dependensi kosong (`[]`) berhubungan dengan "pemasangan" komponen, yaitu ketika komponen ditambahkan ke layar.
+- Dalam *Strict Mode*, React memasang komponen dua kali (hanya dalam pengembangan!) untuk menguji coba *Effect* Anda.
+- Jika *Effect* Anda rusak karena pemasangan ulang, Anda perlu menerapkan fungsi pembersihan.
+- React akan memanggil fungsi pembersihan Anda sebelum *Effect* dijalankan di lain waktu, dan selama proses pelepasan (*unmount*).
-#### Focus a field on mount {/*focus-a-field-on-mount*/}
+#### Memfokuskan bidang teks saat pemasangan {/*focus-a-field-on-mount*/}
-In this example, the form renders a ` ` component.
+Dalam contoh ini, formulir merender komponen ` `.
-Use the input's [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) method to make `MyInput` automatically focus when it appears on the screen. There is already a commented out implementation, but it doesn't quite work. Figure out why it doesn't work, and fix it. (If you're familiar with the `autoFocus` attribute, pretend that it does not exist: we are reimplementing the same functionality from scratch.)
+Gunakan metode [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus) pada input untuk membuat `MyInput` secara otomatis fokus ketika muncul di layar. Sudah ada implementasi yang dikomentari, tetapi tidak cukup berhasil. Cari tahu mengapa hal ini tidak berhasil, dan perbaiki. (Jika Anda terbiasa dengan atribut `autoFocus`, anggap saja atribut tersebut tidak ada: kita mengimplementasikan ulang fungsi yang sama dari awal).
@@ -977,7 +979,7 @@ import { useEffect, useRef } from 'react';
export default function MyInput({ value, onChange }) {
const ref = useRef(null);
- // TODO: This doesn't quite work. Fix it.
+ // TODO: Ini tidak bekerja. Perbaiki.
// ref.current.focus()
return (
@@ -1000,13 +1002,13 @@ export default function Form() {
const [upper, setUpper] = useState(false);
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} form
+ setShow(s => !s)}>{show ? 'Sembunyikan' : 'Tampilkan'} formulir
{show && (
<>
- Enter your name:
+ Masukkan nama:
setName(e.target.value)}
@@ -1018,9 +1020,9 @@ export default function Form() {
checked={upper}
onChange={e => setUpper(e.target.checked)}
/>
- Make it uppercase
+ Jadikan huruf besar
- Hello, {upper ? name.toUpperCase() : name}
+ Halo, {upper ? name.toUpperCase() : name}
>
)}
>
@@ -1043,15 +1045,15 @@ body {
-To verify that your solution works, press "Show form" and verify that the input receives focus (becomes highlighted and the cursor is placed inside). Press "Hide form" and "Show form" again. Verify the input is highlighted again.
+Untuk memastikan bahwa solusi Anda berfungsi, tekan "Tampilkan formulir" dan pastikan bahwa input menerima fokus (menjadi tersorot dan kursor ditempatkan di dalam). Tekan "Sembunyikan formulir" dan "Tampilkan formulir" lagi. Pastikan bahwa input telah disorot kembali.
-`MyInput` should only focus _on mount_ rather than after every render. To verify that the behavior is right, press "Show form" and then repeatedly press the "Make it uppercase" checkbox. Clicking the checkbox should _not_ focus the input above it.
+`MyInput` seharusnya hanya fokus _pada saat pemasangan_, bukan setelah setiap _render_. Untuk memastikan bahwa perilaku sudah benar, tekan "Tampilkan formulir" dan kemudian berulang kali tekan kotak centang "Jadikan huruf besar". Mengklik kotak centang tersebut seharusnya _tidak_ akan memfokuskan input di atasnya.
-Calling `ref.current.focus()` during render is wrong because it is a *side effect*. Side effects should either be placed inside an event handler or be declared with `useEffect`. In this case, the side effect is _caused_ by the component appearing rather than by any specific interaction, so it makes sense to put it in an Effect.
+Memanggil `ref.current.focus()` saat _render_ salah karena ia merupakan _efek samping_. Efek samping seharusnya ditempatkan di dalam _event handler_ atau dideklarasikan dengan `useEffect`. Dalam contoh ini, efek samping _dihasilkan_ oleh komponen yang muncul, bukan oleh interaksi tertentu, oleh karena itu akan masuk akal menempatkannya di dalam _Effect_.
-To fix the mistake, wrap the `ref.current.focus()` call into an Effect declaration. Then, to ensure that this Effect runs only on mount rather than after every render, add the empty `[]` dependencies to it.
+Untuk memperbaiki kesalahannya, bungkus panggilan `ref.current.focus()` di dalam _Effect_. Kemudian, untuk memastikan bahwa _Effect_ ini hanya berjalan pada saat pemasangan dan bukan setelah setiap render, tambahkan dependensi kosong `[]` ke dalamnya.
@@ -1085,13 +1087,13 @@ export default function Form() {
const [upper, setUpper] = useState(false);
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} form
+ setShow(s => !s)}>{show ? 'Sembunyikan' : 'Tampilkan'} formulir
{show && (
<>
- Enter your name:
+ Masukkan nama:
setName(e.target.value)}
@@ -1103,9 +1105,9 @@ export default function Form() {
checked={upper}
onChange={e => setUpper(e.target.checked)}
/>
- Make it uppercase
+ Jadikan huruf besar
- Hello, {upper ? name.toUpperCase() : name}
+ Halo, {upper ? name.toUpperCase() : name}
>
)}
>
@@ -1129,13 +1131,13 @@ body {
-#### Focus a field conditionally {/*focus-a-field-conditionally*/}
+#### Memfokuskan bidang teks secara kondisional {/*focus-a-field-conditionally*/}
-This form renders two ` ` components.
+Formulir ini me-*render* dua komponen ` `.
-Press "Show form" and notice that the second field automatically gets focused. This is because both of the ` ` components try to focus the field inside. When you call `focus()` for two input fields in a row, the last one always "wins".
+Tekan "Tampilkan formulir" dan perhatikan bahwa bidang kedua secara otomatis akan terfokus. Hal ini karena kedua komponen ` ` mencoba untuk memfokuskan bidang di dalamnya. Ketika Anda memanggil `focus()` untuk dua bidang input secara berurutan, bidang input yang terakhir akan selalu "menang".
-Let's say you want to focus the first field. The first `MyInput` component now receives a boolean `shouldFocus` prop set to `true`. Change the logic so that `focus()` is only called if the `shouldFocus` prop received by `MyInput` is `true`.
+Katakanlah Anda ingin memfokuskan bidang pertama. Komponen `MyInput` pertama sekarang menerima *prop* boolean `shouldFocus` yang disetel ke `true`. Ubah logikanya sehingga `focus()` hanya dipanggil jika *prop* `shouldFocus` yang diterima oleh `MyInput` adalah `true`.
@@ -1145,7 +1147,7 @@ import { useEffect, useRef } from 'react';
export default function MyInput({ shouldFocus, value, onChange }) {
const ref = useRef(null);
- // TODO: call focus() only if shouldFocus is true.
+ // TODO: panggil focus() hanya ketika nilai shouldFocus adalah true.
useEffect(() => {
ref.current.focus();
}, []);
@@ -1172,13 +1174,13 @@ export default function Form() {
const name = firstName + ' ' + lastName;
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} form
+ setShow(s => !s)}>{show ? 'Sembunyikan' : 'Tampilkan'} formulir
{show && (
<>
- Enter your first name:
+ Masukkan nama depan:
setFirstName(e.target.value)}
@@ -1186,7 +1188,7 @@ export default function Form() {
/>
- Enter your last name:
+ Masukkan nama belakang:
setLastName(e.target.value)}
@@ -1215,17 +1217,17 @@ body {
-To verify your solution, press "Show form" and "Hide form" repeatedly. When the form appears, only the *first* input should get focused. This is because the parent component renders the first input with `shouldFocus={true}` and the second input with `shouldFocus={false}`. Also check that both inputs still work and you can type into both of them.
+Untuk memastikan solusi Anda, tekan "Tampilkan formulir" dan "Sembunyikan formulir" berulang kali. Ketika formulir muncul, hanya input *pertama* yang akan difokuskan. Hal ini karena komponen induk merender input pertama dengan `shouldFocus={true}` dan input kedua dengan `shouldFocus={false}`. Periksa juga apakah kedua input masih berfungsi dan Anda dapat mengetikkan kedua input tersebut.
-You can't declare an Effect conditionally, but your Effect can include conditional logic.
+Anda tidak dapat mendeklarasikan *Effect* secara kondisional, tetapi *Effect* Anda dapat mengandung logika kondisional.
-Put the conditional logic inside the Effect. You will need to specify `shouldFocus` as a dependency because you are using it inside the Effect. (This means that if some input's `shouldFocus` changes from `false` to `true`, it will focus after mount.)
+Letakkan logika kondisional di dalam Effect. Anda harus menentukan `shouldFocus` sebagai dependensi karena Anda menggunakannya di dalam *Effect*. (Ini berarti bahwa jika `shouldFocus` dari beberapa input berubah dari `false` menjadi `true`, input tersebut akan fokus setelah dipasang.)
@@ -1263,13 +1265,13 @@ export default function Form() {
const name = firstName + ' ' + lastName;
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} form
+ setShow(s => !s)}>{show ? 'Sembunyikan' : 'Tampilkan'} formulir
{show && (
<>
- Enter your first name:
+ Masukkan nama depan:
setFirstName(e.target.value)}
@@ -1277,14 +1279,14 @@ export default function Form() {
/>
- Enter your last name:
+ Masukkan nama belakang:
setLastName(e.target.value)}
shouldFocus={false}
/>
- Hello, {upper ? name.toUpperCase() : name}
+ Halo, {upper ? name.toUpperCase() : name}
>
)}
>
@@ -1308,15 +1310,15 @@ body {
-#### Fix an interval that fires twice {/*fix-an-interval-that-fires-twice*/}
+#### Memperbaiki interval yang menembak dua kali {/*fix-an-interval-that-fires-twice*/}
-This `Counter` component displays a counter that should increment every second. On mount, it calls [`setInterval`.](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) This causes `onTick` to run every second. The `onTick` function increments the counter.
+Komponen `Counter` ini menampilkan penghitung yang harus bertambah setiap detik. Saat dipasang, komponen ini memanggil [`setInterval`.](https://developer.mozilla.org/en-US/docs/Web/API/setInterval) Hal ini menyebabkan `onTick` berjalan setiap detik. Fungsi `onTick` menambah penghitung.
-However, instead of incrementing once per second, it increments twice. Why is that? Find the cause of the bug and fix it.
+Namun, alih-alih bertambah sekali per detik, fungsi ini bertambah dua kali. Mengapa demikian? Temukan penyebab bug dan perbaiki.
-Keep in mind that `setInterval` returns an interval ID, which you can pass to [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) to stop the interval.
+Perlu diingat bahwa `setInterval` mengembalikan ID interval, yang dapat Anda berikan kepada [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) untuk menghentikan interval.
@@ -1348,7 +1350,7 @@ export default function Form() {
const [show, setShow] = useState(false);
return (
<>
- setShow(s => !s)}>{show ? 'Hide' : 'Show'} counter
+ setShow(s => !s)}>{show ? 'Sembunyikan' : 'Tampilkan'} penghitung
{show && }
@@ -1373,11 +1375,11 @@ body {
-When [Strict Mode](/reference/react/StrictMode) is on (like in the sandboxes on this site), React remounts each component once in development. This causes the interval to be set up twice, and this is why each second the counter increments twice.
+Ketika [*Strict Mode*](/reference/react/StrictMode) diaktifkan (seperti pada sandbox di situs ini), React akan memasang ulang setiap komponen satu kali dalam pengembangan. Hal ini menyebabkan interval disetel dua kali, dan inilah mengapa setiap detik penghitung bertambah dua kali.
-However, React's behavior is not the *cause* of the bug: the bug already exists in the code. React's behavior makes the bug more noticeable. The real cause is that this Effect starts a process but doesn't provide a way to clean it up.
+Namun, perilaku React bukanlah *penyebab* dari bug: bug sudah ada di dalam kode. Perilaku React membuat bug menjadi lebih terlihat. Penyebab sebenarnya adalah karena *Effect* ini memulai sebuah proses tetapi tidak menyediakan cara untuk membersihkannya.
-To fix this code, save the interval ID returned by `setInterval`, and implement a cleanup function with [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval):
+Untuk memperbaiki kode ini, simpan ID interval yang dikembalikan oleh `setInterval`, dan terapkan fungsi pembersihan dengan [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval):
@@ -1431,13 +1433,13 @@ body {
-In development, React will still remount your component once to verify that you've implemented cleanup well. So there will be a `setInterval` call, immediately followed by `clearInterval`, and `setInterval` again. In production, there will be only one `setInterval` call. The user-visible behavior in both cases is the same: the counter increments once per second.
+Dalam mode pengembangan, React masih akan memasang ulang komponen Anda satu kali untuk memastikan bahwa Anda telah mengimplementasikan pembersihan dengan baik. Jadi akan ada pemanggilan `setInterval`, segera diikuti dengan `clearInterval`, dan `setInterval` lagi. Dalam produksi, hanya akan ada satu panggilan `setInterval`. Perilaku yang terlihat oleh pengguna pada kedua kasus tersebut adalah sama: penghitung bertambah satu kali per detik.
-#### Fix fetching inside an Effect {/*fix-fetching-inside-an-effect*/}
+#### Memperbaiki pengambilan data di dalam *Effect* {/*fix-fetching-inside-an-effect*/}
-This component shows the biography for the selected person. It loads the biography by calling an asynchronous function `fetchBio(person)` on mount and whenever `person` changes. That asynchronous function returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which eventually resolves to a string. When fetching is done, it calls `setBio` to display that string under the select box.
+Komponen ini menampilkan biografi orang yang dipilih. Komponen ini memuat biografi dengan memanggil fungsi asinkron `fetchBio(person)` pada saat pemasangan dan setiap kali `person` berubah. Fungsi asinkron tersebut mengembalikan sebuah [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) yang pada akhirnya akan menjadi sebuah string. Ketika pengambilan selesai, fungsi ini memanggil `setBio` untuk menampilkan string tersebut di bawah kotak pilihan.
@@ -1466,7 +1468,7 @@ export default function Page() {
Taylor
- {bio ?? 'Loading...'}
+ {bio ?? 'Memuat...'}
>
);
}
@@ -1477,7 +1479,7 @@ export async function fetchBio(person) {
const delay = person === 'Bob' ? 2000 : 200;
return new Promise(resolve => {
setTimeout(() => {
- resolve('This is ' + person + '’s bio.');
+ resolve('Ini adalah biodata ' + person + '.');
}, delay);
})
}
@@ -1487,30 +1489,30 @@ export async function fetchBio(person) {
-There is a bug in this code. Start by selecting "Alice". Then select "Bob" and then immediately after that select "Taylor". If you do this fast enough, you will notice that bug: Taylor is selected, but the paragraph below says "This is Bob's bio."
+Ada bug dalam kode ini. Mulailah dengan memilih "Alice". Kemudian pilih "Bob" dan segera setelah itu pilih "Taylor". Jika Anda melakukan ini dengan cukup cepat, Anda akan melihat bug itu: Taylor sudah dipilih, tetapi paragraf di bawahnya mengatakan "Ini adalah biodata Bob."
-Why does this happen? Fix the bug inside this Effect.
+Mengapa hal ini terjadi? Perbaiki bug di dalam *Effect* ini.
-If an Effect fetches something asynchronously, it usually needs cleanup.
+Jika *Effect* melakukan *fetching* terhadap sesuatu secara asinkron, biasanya perlu dibersihkan.
-To trigger the bug, things need to happen in this order:
+Untuk memicu bug, beberapa hal harus terjadi secara berurutan:
-- Selecting `'Bob'` triggers `fetchBio('Bob')`
-- Selecting `'Taylor'` triggers `fetchBio('Taylor')`
-- **Fetching `'Taylor'` completes *before* fetching `'Bob'`**
-- The Effect from the `'Taylor'` render calls `setBio('This is Taylor’s bio')`
-- Fetching `'Bob'` completes
-- The Effect from the `'Bob'` render calls `setBio('This is Bob’s bio')`
+- Memilih `'Bob'` akan memicu `fetchBio('Bob')`
+- Memilih `'Taylor'` akan memicu `fetchBio('Taylor')`
+- **Mengambil data `'Taylor'` selesai *sebelum* mengambil data `'Bob'`**
+- *Effect* dari *render* `'Taylor'` memanggil `setBio('Ini adalah biodata Taylor')`
+- Pengambilan data `'Bob'` selesai
+- *Effect* dari *render* `'Bob'` memanggil `setBio('Ini adalah biodata Bob')`
-This is why you see Bob's bio even though Taylor is selected. Bugs like this are called [race conditions](https://en.wikipedia.org/wiki/Race_condition) because two asynchronous operations are "racing" with each other, and they might arrive in an unexpected order.
+Inilah sebabnya mengapa Anda melihat biodata Bob meskipun Taylor yang terpilih. Bug seperti ini disebut [*race condition*](https://en.wikipedia.org/wiki/Race_condition) karena dua operasi asinkron "berlomba" satu sama lain, dan mungkin tiba dalam urutan yang tidak terduga.
-To fix this race condition, add a cleanup function:
+Untuk memperbaiki *race condition* ini, tambahkan fungsi pembersihan:
@@ -1544,7 +1546,7 @@ export default function Page() {
Taylor
- {bio ?? 'Loading...'}
+ {bio ?? 'Memuat...'}
>
);
}
@@ -1555,7 +1557,7 @@ export async function fetchBio(person) {
const delay = person === 'Bob' ? 2000 : 200;
return new Promise(resolve => {
setTimeout(() => {
- resolve('This is ' + person + '’s bio.');
+ resolve('Ini adalah biodata ' + person + '.');
}, delay);
})
}
@@ -1564,16 +1566,16 @@ export async function fetchBio(person) {
-Each render's Effect has its own `ignore` variable. Initially, the `ignore` variable is set to `false`. However, if an Effect gets cleaned up (such as when you select a different person), its `ignore` variable becomes `true`. So now it doesn't matter in which order the requests complete. Only the last person's Effect will have `ignore` set to `false`, so it will call `setBio(result)`. Past Effects have been cleaned up, so the `if (!ignore)` check will prevent them from calling `setBio`:
+Setiap *Effect* *render* memiliki variabel `ignore` sendiri. Awalnya, variabel `ignore` disetel ke `false`. Namun, jika sebuah Effect dibersihkan (seperti ketika Anda memilih orang yang berbeda), variabel `ignore` menjadi `true`. Jadi sekarang tidak masalah dalam urutan mana permintaan diselesaikan. Hanya *Effect* orang terakhir yang memiliki `ignore` yang disetel ke `false`, sehingga akan memanggil `setBio(result)`. Efek-efek sebelumnya telah dibersihkan, sehingga pemeriksaan `if (!ignore)` akan mencegah mereka memanggil `setBio`:
-- Selecting `'Bob'` triggers `fetchBio('Bob')`
-- Selecting `'Taylor'` triggers `fetchBio('Taylor')` **and cleans up the previous (Bob's) Effect**
-- Fetching `'Taylor'` completes *before* fetching `'Bob'`
-- The Effect from the `'Taylor'` render calls `setBio('This is Taylor’s bio')`
-- Fetching `'Bob'` completes
-- The Effect from the `'Bob'` render **does not do anything because its `ignore` flag was set to `true`**
+- Memilih `'Bob'` akan memicu `fetchBio('Bob')`
+- Memilih `'Taylor'` akan memicu `fetchBio('Taylor')` **dan membersihkan Efek sebelumnya (milik Bob)**
+- Mengambil data `'Taylor'` selesai *sebelum* mengambil data `'Bob'`
+- *Effect* dari render `'Taylor'` memanggil `setBio('Ini adalah biodata Taylor')`
+- Mengambil data `'Bob'` selesai
+- *Effect* dari *render* `'Bob'` **tidak melakukan apa pun karena flag `ignore` diset ke `true`**
-In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `ignore` is the most reliable way to fix this type of problems.
+Selain mengabaikan hasil panggilan API yang sudah usang, Anda juga dapat menggunakan [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) untuk membatalkan *request* yang sudah tidak diperlukan. Namun, hal ini saja tidak cukup untuk melindungi dari *race condition*. Langkah-langkah asinkron lainnya dapat dirangkai (*chain*) setelah *fetch*, jadi menggunakan tanda spesifik seperti `ignore` adalah cara paling reliabel untuk memperbaiki masalah seperti ini.
diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json
index 82fa490a7..f491bbcbf 100644
--- a/src/sidebarLearn.json
+++ b/src/sidebarLearn.json
@@ -177,7 +177,7 @@
"path": "/learn/manipulating-the-dom-with-refs"
},
{
- "title": "Synchronizing with Effects",
+ "title": "Sinkronisasi dengan Effect",
"path": "/learn/synchronizing-with-effects"
},
{