diff --git a/src/content/learn/escape-hatches.md b/src/content/learn/escape-hatches.md index ecaa4c49a..4c90ebf1e 100644 --- a/src/content/learn/escape-hatches.md +++ b/src/content/learn/escape-hatches.md @@ -193,7 +193,7 @@ Baca **[Sinkronisasi dengan *Effect*](/learn/synchronizing-with-effects)** untuk ## Anda mungkin tidak membutuhkan *Effect* {/*you-might-not-need-an-effect*/} -*Effects* adalah sebuah jalan keluar dari paradigma React. Mereka membiarkan Anda untuk "keluar" dari React dan menyinkronkan komponen Anda dengan beberapa sistem eksternal. Jika tidak ada sistem eksternal yang terlibat (misalkan, jika Anda ingin memperbarui *state* komponen dengan beberapa *props* atau perubahan *state*), Anda seharusnya tidak perlu menggunakan sebuah *Effect*. Hilangkan *Effects* yang tidak pelu akan membuat kode Anda lebih mudah untuk diikuti, lebih cepat untuk dijalankan, dan lebih sedikit berpotensi galat. +*Effects* adalah jalan keluar dari paradigma React. Mereka memungkinkan Anda "keluar" dari React dan menyinkronkan komponen Anda dengan sistem eksternal. Jika tidak ada sistem eksternal yang terlibat (misalkan, jika Anda ingin memperbarui *state* komponen dengan beberapa *props* atau perubahan *state*), Anda seharusnya tidak perlu menggunakan *Effect*. Hilangkan *Effects* yang tidak perlu akan membuat kode Anda lebih mudah untuk diikuti, lebih cepat untuk dijalankan, dan lebih sedikit berpotensi galat. Ada dua kasus umum di mana Anda tidak memerlukan *Effects*: - **Anda tidak perlu menggunakan *Effects* untuk mengubah data saat pe-*render*-an.** diff --git a/src/content/learn/you-might-not-need-an-effect.md b/src/content/learn/you-might-not-need-an-effect.md index 66cdc3117..5342a27ed 100644 --- a/src/content/learn/you-might-not-need-an-effect.md +++ b/src/content/learn/you-might-not-need-an-effect.md @@ -1,45 +1,45 @@ --- -title: 'You Might Not Need an Effect' +title: 'Anda Mungkin Tidak Membutuhkan Effect' --- -Effects are an escape hatch from the React paradigm. They let you "step outside" of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM. If there is no external system involved (for example, if you want to update a component's state when some props or state change), you shouldn't need an Effect. Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone. +*Effects* adalah jalan keluar dari paradigma React. Mereka memungkinkan Anda untuk "keluar" dari React dan menyinkronkan komponen Anda dengan sistem eksternal. Jika tidak ada sistem eksternal yang terlibat (misalkan, jika Anda ingin memperbarui *state* komponen dengan beberapa *props* atau perubahan *state*), Anda seharusnya tidak perlu menggunakan *Effect*. Menghilangkan *Effects* yang tidak perlu akan membuat kode Anda lebih mudah untuk diikuti, lebih cepat untuk dijalankan, dan lebih sedikit berpotensi galat. -* Why and how to remove unnecessary Effects from your components -* How to cache expensive computations without Effects -* How to reset and adjust component state without Effects -* How to share logic between event handlers -* Which logic should be moved to event handlers -* How to notify parent components about changes +* Mengapa dan cara menghapus *Effects* yang tidak perlu dari komponen Anda +* Cara meng-*cache* komputasi yang mahal tanpa *Effects* +* Cara menyetel ulang dan mengatur *state* komponen tanpa *Effects* +* Cara berbagi logika di antara *event handler* share logic between event handlers +* Logika apa yang seharusnya dipindahkan ke *event handler* +* Cara memberi tahu perubahan komponen ke komponen induk -## How to remove unnecessary Effects {/*how-to-remove-unnecessary-effects*/} +## Cara menghapus Effect yang tidak perlu {/*how-to-remove-unnecessary-effects*/} -There are two common cases in which you don't need Effects: +Ada dua kasus umum di mana Anda tidak memerlukan *Effects*: -* **You don't need Effects to transform data for rendering.** For example, let's say you want to filter a list before displaying it. You might feel tempted to write an Effect that updates a state variable when the list changes. However, this is inefficient. When you update the state, React will first call your component functions to calculate what should be on the screen. Then React will ["commit"](/learn/render-and-commit) these changes to the DOM, updating the screen. Then React will run your Effects. If your Effect *also* immediately updates the state, this restarts the whole process from scratch! To avoid the unnecessary render passes, transform all the data at the top level of your components. That code will automatically re-run whenever your props or state change. -* **You don't need Effects to handle user events.** For example, let's say you want to send an `/api/buy` POST request and show a notification when the user buys a product. In the Buy button click event handler, you know exactly what happened. By the time an Effect runs, you don't know *what* the user did (for example, which button was clicked). This is why you'll usually handle user events in the corresponding event handlers. +* **Anda tidak memerlukan *Effects* untuk melakukan transformasi data untuk *rendering*.** Sebagai contoh, katakanlah Anda ingin melakukan *filter* terhadap sebuah daftar sebelum menampiklannya. Anda mungkin merasa tergoda untuk menulis *Effect* yang memperbarui variabel *state* ketika daftar berubah. Akan tetapi, hal ini tidak efisien. Ketika Anda memperbarui *state*, React akan memanggil fungsi komponen Anda terlebih dahulu untuk menghitung apa yang seharusnya ada di layar. Kemudian React akan ["*commit*"](/learn/render-and-commit) perubahan ini ke DOM, memperbarui layar. Kemudian React akan menjalankan *Effect* Anda. Jika *Effect* Anda *juga* segera memperbarui state, ini akan mengulang seluruh proses dari awal! Untuk menghindari *render pass* yang tidak perlu, ubah semua data pada tingkat teratas komponen Anda. Kode tersebut akan secara otomatis dijalankan ulang setiap kali *props* atau *state* anda berubah. +* **Anda tidak memerlukan Efek untuk menangani *event* dari pengguna.** Sebagai contoh, katakanlah Anda ingin mengirim *request* POST `/api/buy` dan menampilkan notifikasi ketika pengguna membeli produk. Di *event handler* klik tombol Beli, Anda tahu persis apa yang terjadi. Pada saat *Effect* berjalan, Anda tidak tahu *apa* yang dilakukan pengguna (misalnya, tombol mana yang diklik). Inilah sebabnya mengapa Anda biasanya akan menangani *event* pengguna di *event handler* yang sesuai. -You *do* need Effects to [synchronize](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events) with external systems. For example, you can write an Effect that keeps a jQuery widget synchronized with the React state. You can also fetch data with Effects: for example, you can synchronize the search results with the current search query. Keep in mind that modern [frameworks](/learn/start-a-new-react-project#production-grade-react-frameworks) provide more efficient built-in data fetching mechanisms than writing Effects directly in your components. +Anda *memang* membutuhkan Effect untuk [melakukan sinkronisasi](/learn/synchronizing-with-effects#what-are-effects-and-how-are-they-different-from-events) dengan sistem eksternal. dengan sistem eksternal. Sebagai contoh, Anda dapat menulis sebuah *Effect* yang membuat *widget* jQuery tetap tersinkronisasi dengan *state* React. Anda juga dapat mengambil data dengan *Effect*: sebagai contoh, Anda dapat menyinkronkan hasil pencarian dengan kueri pencarian saat ini. Perlu diingat bahwa [kerangka kerja (*framework*)](/learn/start-a-new-react-project#production-grade-react-frameworks) modern menyediakan mekanisme pengambilan data bawaan yang lebih efisien daripada menulis *Effect* secara langsung di dalam komponen Anda. -To help you gain the right intuition, let's look at some common concrete examples! +Untuk membantu Anda mendapatkan intuisi yang tepat, mari kita lihat beberapa contoh konkret yang umum! -### Updating state based on props or state {/*updating-state-based-on-props-or-state*/} +### Memperbarui *state* berdasarkan *props* atau *state* {/*updating-state-based-on-props-or-state*/} -Suppose you have a component with two state variables: `firstName` and `lastName`. You want to calculate a `fullName` from them by concatenating them. Moreover, you'd like `fullName` to update whenever `firstName` or `lastName` change. Your first instinct might be to add a `fullName` state variable and update it in an Effect: +Katakanlah Anda memiliki komponen dengan dua variabel *state*: `firstName` dan `lastName`. Anda ingin mendapatkan `fullName` dengan menggabungkan keduanya. Selain itu, Anda ingin `fullName` diperbarui setiap kali `firstName` atau `lastName` berubah. Naluri pertama Anda mungkin menambahkan variabel *state* `fullName` dan memperbaruinya di *Effect*: ```js {5-9} function Form() { const [firstName, setFirstName] = useState('Taylor'); const [lastName, setLastName] = useState('Swift'); - // πŸ”΄ Avoid: redundant state and unnecessary Effect + // πŸ”΄ Hindari: state berlebihan dan Effect yang tidak perlu const [fullName, setFullName] = useState(''); useEffect(() => { setFullName(firstName + ' ' + lastName); @@ -48,29 +48,29 @@ function Form() { } ``` -This is more complicated than necessary. It is inefficient too: it does an entire render pass with a stale value for `fullName`, then immediately re-renders with the updated value. Remove the state variable and the Effect: +Ini lebih rumit dari yang diperlukan. Ini juga tidak efisien: ia melakukan *render pass* secara keseluruhan dengan nilai usang untuk `fullName`, lalu segera me-*render* ulang dengan nilai yang diperbarui. Hapus variabel *state* dan *Effect*: ```js {4-5} function Form() { const [firstName, setFirstName] = useState('Taylor'); const [lastName, setLastName] = useState('Swift'); - // βœ… Good: calculated during rendering + // βœ… Baik: dikalkulasi saat render const fullName = firstName + ' ' + lastName; // ... } ``` -**When something can be calculated from the existing props or state, [don't put it in state.](/learn/choosing-the-state-structure#avoid-redundant-state) Instead, calculate it during rendering.** This makes your code faster (you avoid the extra "cascading" updates), simpler (you remove some code), and less error-prone (you avoid bugs caused by different state variables getting out of sync with each other). If this approach feels new to you, [Thinking in React](/learn/thinking-in-react#step-3-find-the-minimal-but-complete-representation-of-ui-state) explains what should go into state. +**Ketika sebuah nilai dapat dihitung dari *props* atau *state* yang ada, [jangan memasukkannya ke dalam *state*.](/learn/choosing-the-state-structure#avoid-redundant-state) Sebaiknya, hitunglah saat *rendering*.** Hal ini membuat kode Anda lebih cepat (Anda menghindari pembaruan "bertingkat" tambahan), lebih sederhana (Anda menghapus beberapa kode), dan lebih tidak rawan terhadap *error* (Anda menghindari bug yang disebabkan oleh variabel *state* berbeda yang tidak sinkron satu sama lain). Jika pendekatan ini terasa baru bagi Anda, [Cara Berpikir dengan React](/learn/thinking-in-react#step-3-find-the-minimal-but-complete-representation-of-ui-state) menjelaskan apa yang seharusnya masuk sebagai *state*. -### Caching expensive calculations {/*caching-expensive-calculations*/} +### Menyimpan penghitungan mahal di *cache* {/*caching-expensive-calculations*/} -This component computes `visibleTodos` by taking the `todos` it receives by props and filtering them according to the `filter` prop. You might feel tempted to store the result in state and update it from an Effect: +Komponen ini menghitung `visibleTodos` dengan mengambil `todos` yang diterimanya berdasarkan *props* dan memfilternya berdasarkan prop `filter`. Anda mungkin tergoda untuk menyimpan hasilnya dalam keadaan dan memperbaruinya dari *Effect*: ```js {4-8} function TodoList({ todos, filter }) { const [newTodo, setNewTodo] = useState(''); - // πŸ”΄ Avoid: redundant state and unnecessary Effect + // πŸ”΄ Hindari: state berlebihan dan Effect yang tidak perlu const [visibleTodos, setVisibleTodos] = useState([]); useEffect(() => { setVisibleTodos(getFilteredTodos(todos, filter)); @@ -80,20 +80,20 @@ function TodoList({ todos, filter }) { } ``` -Like in the earlier example, this is both unnecessary and inefficient. First, remove the state and the Effect: +Seperti pada contoh sebelumnya, hal ini tidak diperlukan dan tidak efisien. Pertama, hapus *state* dan *Effect*-nya: ```js {3-4} function TodoList({ todos, filter }) { const [newTodo, setNewTodo] = useState(''); - // βœ… This is fine if getFilteredTodos() is not slow. + // βœ… Ini baik jika getFilteredTodos() tidak lambat. const visibleTodos = getFilteredTodos(todos, filter); // ... } ``` -Usually, this code is fine! But maybe `getFilteredTodos()` is slow or you have a lot of `todos`. In that case you don't want to recalculate `getFilteredTodos()` if some unrelated state variable like `newTodo` has changed. +Biasanya, kode ini baik-baik saja! Namun mungkin `getFilteredTodos()` lambat atau Anda memiliki banyak `todos`. Dalam hal ini Anda tidak ingin mengkalkulasi ulang `getFilteredTodos()` jika beberapa variabel *state* yang tidak terkait seperti `newTodo` telah berubah. -You can cache (or ["memoize"](https://en.wikipedia.org/wiki/Memoization)) an expensive calculation by wrapping it in a [`useMemo`](/reference/react/useMemo) Hook: +Anda dapat melakukan *cache* (atau ["memoisasi"](https://en.wikipedia.org/wiki/Memoization)) perhitungan yang mahal dengan membungkusnya dalam Hook [`useMemo`](/reference/react/useMemo): ```js {5-8} import { useMemo, useState } from 'react'; @@ -101,14 +101,14 @@ import { useMemo, useState } from 'react'; function TodoList({ todos, filter }) { const [newTodo, setNewTodo] = useState(''); const visibleTodos = useMemo(() => { - // βœ… Does not re-run unless todos or filter change + // βœ… Tidak dijalankan ulang kecuali todos atau filter berubah return getFilteredTodos(todos, filter); }, [todos, filter]); // ... } ``` -Or, written as a single line: +Atau, ditulis dalam satu baris: ```js {5-6} import { useMemo, useState } from 'react'; @@ -121,15 +121,15 @@ function TodoList({ todos, filter }) { } ``` -**This tells React that you don't want the inner function to re-run unless either `todos` or `filter` have changed.** React will remember the return value of `getFilteredTodos()` during the initial render. During the next renders, it will check if `todos` or `filter` are different. If they're the same as last time, `useMemo` will return the last result it has stored. But if they are different, React will call the inner function again (and store its result). +**Hal ini memberitahu React bahwa Anda tidak ingin fungsi di dalamnya dijalankan ulang kecuali `todos` atau `filter` telah berubah.** React akan mengingat nilai kembalian `getFilteredTodos()` selama *render* awal. Selama *rendering* berikutnya, ia akan memeriksa apakah `todos` atau `filter` berbeda. Jika sama dengan yang terakhir kali, `useMemo` akan mengembalikan hasil terakhir yang disimpannya. Namun jika berbeda, React akan memanggil fungsi di dalamnya lagi (dan menyimpan hasilnya). -The function you wrap in [`useMemo`](/reference/react/useMemo) runs during rendering, so this only works for [pure calculations.](/learn/keeping-components-pure) +Fungsi yang Anda bungkus dalam [`useMemo`](/reference/react/useMemo) berjalan selama rendering, jadi ini hanya berfungsi untuk [perhitungan murni.](/learn/keeping-components-pure) -#### How to tell if a calculation is expensive? {/*how-to-tell-if-a-calculation-is-expensive*/} +#### Bagaimana cara mengetahui apakah suatu perhitungan itu mahal? {/*how-to-tell-if-a-calculation-is-expensive*/} -In general, unless you're creating or looping over thousands of objects, it's probably not expensive. If you want to get more confidence, you can add a console log to measure the time spent in a piece of code: +Secara umum, kecuali Anda membuat atau melakukan pengulangan terhadap ribuan objek, biayanya mungkin tidak mahal. Jika Anda ingin lebih percaya diri, Anda dapat menambahkan log konsol untuk mengukur waktu yang dihabiskan dalam sebuah kode: ```js {1,3} console.time('filter array'); @@ -137,7 +137,7 @@ const visibleTodos = getFilteredTodos(todos, filter); console.timeEnd('filter array'); ``` -Perform the interaction you're measuring (for example, typing into the input). You will then see logs like `filter array: 0.15ms` in your console. If the overall logged time adds up to a significant amount (say, `1ms` or more), it might make sense to memoize that calculation. As an experiment, you can then wrap the calculation in `useMemo` to verify whether the total logged time has decreased for that interaction or not: +Lakukan interaksi yang Anda ukur (misalnya, mengetik pada input). Anda kemudian akan melihat log seperti `filter array: 0,15ms` di konsol Anda. Jika keseluruhan waktu yang dicatat bertambah hingga jumlah yang signifikan (katakanlah, `1 ms` atau lebih), mungkin masuk akal untuk melakukan memoisasi terhadap penghitungan tersebut. Sebagai percobaan, Anda kemudian dapat menggabungkan penghitungan dalam `useMemo` untuk memverifikasi apakah total waktu yang dicatat untuk interaksi tersebut telah berkurang atau tidak: ```js console.time('filter array'); @@ -147,23 +147,23 @@ const visibleTodos = useMemo(() => { console.timeEnd('filter array'); ``` -`useMemo` won't make the *first* render faster. It only helps you skip unnecessary work on updates. +`useMemo` tidak akan membuat rendering *pertama* lebih cepat. Ini hanya membantu Anda melewatkan pekerjaan pembaruan yang tidak perlu. -Keep in mind that your machine is probably faster than your users' so it's a good idea to test the performance with an artificial slowdown. For example, Chrome offers a [CPU Throttling](https://developer.chrome.com/blog/new-in-devtools-61/#throttling) option for this. +Ingatlah bahwa perangkat Anda mungkin lebih cepat daripada perangkat pengguna Anda, jadi sebaiknya uji kinerjanya dengan pelambatan buatan. Misalnya, Chrome menawarkan opsi [CPU Throttling](https://developer.chrome.com/blog/new-in-devtools-61/#throttling) untuk ini. -Also note that measuring performance in development will not give you the most accurate results. (For example, when [Strict Mode](/reference/react/StrictMode) is on, you will see each component render twice rather than once.) To get the most accurate timings, build your app for production and test it on a device like your users have. +Perhatikan juga bahwa mengukur kinerja dalam mode pengembangan tidak akan memberi Anda hasil yang paling akurat. (Misalnya, ketika [Strict Mode](/reference/react/StrictMode) aktif, Anda akan melihat setiap komponen dirender dua kali, bukan sekali.) Untuk mendapatkan pengaturan waktu yang paling akurat, kompilasi aplikasi Anda dalam mode produksi dan uji pada perangkat seperti yang dimiliki pengguna Anda. -### Resetting all state when a prop changes {/*resetting-all-state-when-a-prop-changes*/} +### Menyetel ulang keseluruhan *state* ketika *props* berubah {/*resetting-all-state-when-a-prop-changes*/} -This `ProfilePage` component receives a `userId` prop. The page contains a comment input, and you use a `comment` state variable to hold its value. One day, you notice a problem: when you navigate from one profile to another, the `comment` state does not get reset. As a result, it's easy to accidentally post a comment on a wrong user's profile. To fix the issue, you want to clear out the `comment` state variable whenever the `userId` changes: +Komponen `ProfilePage` ini menerima *prop* `userId`. Halaman tersebut berisi *input* komentar, dan Anda menggunakan variabel *state* `comment` untuk menyimpan nilainya. Suatu hari, Anda melihat masalah: saat Anda bernavigasi dari satu profil ke profil lainnya, *state* `comment` tidak disetel ulang. Akibatnya, Anda dapat dengan mudah mengirim komentar ke profil pengguna yang salah secara tidak sengaja. Untuk memperbaiki masalah ini, Anda ingin menghapus variabel *state* `comment` setiap kali `userId` berubah: ```js {4-7} export default function ProfilePage({ userId }) { const [comment, setComment] = useState(''); - // πŸ”΄ Avoid: Resetting state on prop change in an Effect + // πŸ”΄ Hindari: menyetel ulanh state setiap prop berubah di dalam Effect useEffect(() => { setComment(''); }, [userId]); @@ -171,9 +171,9 @@ export default function ProfilePage({ userId }) { } ``` -This is inefficient because `ProfilePage` and its children will first render with the stale value, and then render again. It is also complicated because you'd need to do this in *every* component that has some state inside `ProfilePage`. For example, if the comment UI is nested, you'd want to clear out nested comment state too. +Hal ini tidak efisien karena `ProfilePage` dan turunannya akan di-*render* terlebih dahulu dengan nilai yang sudah usang, lalu di-*render* lagi. Ini juga rumit karena Anda harus melakukan ini di *setiap* komponen yang memiliki *state* di dalam `ProfilePage`. Misalnya, jika UI komentar disarangkan, Anda juga ingin menghapus *state* komentar yang disarangkan. -Instead, you can tell React that each user's profile is conceptually a _different_ profile by giving it an explicit key. Split your component in two and pass a `key` attribute from the outer component to the inner one: +Sebagai gantinya, Anda dapat memberi tahu React bahwa setiap profil pengguna secara konseptual adalah profil _berbeda_ dengan memberinya *key* secara eksplisit. Pisahkan komponen Anda menjadi dua dan oper atribut `key` dari komponen luar ke komponen dalam: ```js {5,11-12} export default function ProfilePage({ userId }) { @@ -186,28 +186,28 @@ export default function ProfilePage({ userId }) { } function Profile({ userId }) { - // βœ… This and any other state below will reset on key change automatically + // βœ… State ini dan state lain di bawahnya akan disetel ulang secara otomatis setiap kali key berubah const [comment, setComment] = useState(''); // ... } ``` -Normally, React preserves the state when the same component is rendered in the same spot. **By passing `userId` as a `key` to the `Profile` component, you're asking React to treat two `Profile` components with different `userId` as two different components that should not share any state.** Whenever the key (which you've set to `userId`) changes, React will recreate the DOM and [reset the state](/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key) of the `Profile` component and all of its children. Now the `comment` field will clear out automatically when navigating between profiles. +Biasanya, React mempertahankan *state* ketika komponen yang sama dirender di tempat yang sama. **Dengan mengoper `userId` sebagai `key` ke komponen `Profile`, Anda meminta React untuk memperlakukan dua komponen `Profile` dengan `userId` yang berbeda sebagai dua komponen berbeda yang tidak boleh berbagi *state* apa pun.** Kapan pun *key* (yang telah Anda setel ke `userId`) berubah, React akan membuat ulang DOM dan [mengatur ulang *state*](/learn/preserving-and-resetting-state#option-2-resetting-state-with-a-key) dari komponen `Profile` dan semua turunannya. Sekarang bidang `comment` akan dihapus secara otomatis saat bernavigasi antar profil. -Note that in this example, only the outer `ProfilePage` component is exported and visible to other files in the project. Components rendering `ProfilePage` don't need to pass the key to it: they pass `userId` as a regular prop. The fact `ProfilePage` passes it as a `key` to the inner `Profile` component is an implementation detail. +Perhatikan bahwa dalam contoh ini, hanya komponen `ProfilePage` bagian luar yang diekspor dan terlihat oleh file lain dalam proyek. Komponen yang merender `ProfilePage` tidak perlu meneruskan kuncinya: komponen meneruskan `userId` sebagai prop biasa. Fakta bahwa `ProfilePage` meneruskannya sebagai `key` ke komponen `Profile` bagian dalam adalah detail implementasi. -### Adjusting some state when a prop changes {/*adjusting-some-state-when-a-prop-changes*/} +### Menyesuaikan sebagian *state* ketika *prop* berubah {/*adjusting-some-state-when-a-prop-changes*/} -Sometimes, you might want to reset or adjust a part of the state on a prop change, but not all of it. +Terkadang, Anda mungkin ingin menyetel ulang atau menyesuaikan sebagian *state* pada perubahan prop, namun tidak semuanya. -This `List` component receives a list of `items` as a prop, and maintains the selected item in the `selection` state variable. You want to reset the `selection` to `null` whenever the `items` prop receives a different array: +Komponen `List` ini menerima *list* `item` sebagai *prop*, dan mempertahankan item yang dipilih dalam variabel *state* `selection`. Anda ingin menyetel ulang `selection` ke `null` setiap kali prop `items` menerima senarai yang berbeda: ```js {5-8} function List({ items }) { const [isReverse, setIsReverse] = useState(false); const [selection, setSelection] = useState(null); - // πŸ”΄ Avoid: Adjusting state on prop change in an Effect + // πŸ”΄ Hindari: Mengatur state saat prop berubah di dalam Effect useEffect(() => { setSelection(null); }, [items]); @@ -215,16 +215,16 @@ function List({ items }) { } ``` -This, too, is not ideal. Every time the `items` change, the `List` and its child components will render with a stale `selection` value at first. Then React will update the DOM and run the Effects. Finally, the `setSelection(null)` call will cause another re-render of the `List` and its child components, restarting this whole process again. +Hal ini juga tidak ideal. Setiap kali `items` berubah, `List` dan komponen turunannya akan di-*render* dengan nilai `selection` yang usang pada awalnya. Kemudian React akan memperbarui DOM dan menjalankan *Effect*-nya. Terakhir, panggilan `setSelection(null)` akan menyebabkan *rendering* ulang `List` dan komponen turunannya lagi, sehingga memulai kembali seluruh proses ini. -Start by deleting the Effect. Instead, adjust the state directly during rendering: +Mulailah dengan menghapus *Effect*. Sebagai gantinya, sesuaikan *state* secara langsung selama rendering: ```js {5-11} function List({ items }) { const [isReverse, setIsReverse] = useState(false); const [selection, setSelection] = useState(null); - // Better: Adjust the state while rendering + // Lebih baik: Menyesuaikan state saat rendering const [prevItems, setPrevItems] = useState(items); if (items !== prevItems) { setPrevItems(items); @@ -234,31 +234,31 @@ function List({ items }) { } ``` -[Storing information from previous renders](/reference/react/useState#storing-information-from-previous-renders) like this can be hard to understand, but it’s better than updating the same state in an Effect. In the above example, `setSelection` is called directly during a render. React will re-render the `List` *immediately* after it exits with a `return` statement. React has not rendered the `List` children or updated the DOM yet, so this lets the `List` children skip rendering the stale `selection` value. +[Menyimpan informasi dari *render* sebelumnya](/reference/react/useState#storing-information-from-previous-renders) seperti ini mungkin sulit untuk dipahami, tetapi ini lebih baik daripada memperbarui *state* yang sama dalam suatu *Effect*. Dalam contoh di atas, `setSelection` dipanggil secara langsung saat *render*. React akan me-*render* ulang `List` *segera* setelah keluar dengan pernyataan `return`. React belum merender turunan `List` atau memperbarui DOM, jadi hal ini memungkinkan turunan `List` melewatkan rendering nilai `selection` yang sudah usang. -When you update a component during rendering, React throws away the returned JSX and immediately retries rendering. To avoid very slow cascading retries, React only lets you update the *same* component's state during a render. If you update another component's state during a render, you'll see an error. A condition like `items !== prevItems` is necessary to avoid loops. You may adjust state like this, but any other side effects (like changing the DOM or setting timeouts) should stay in event handlers or Effects to [keep components pure.](/learn/keeping-components-pure) +Saat Anda memperbarui komponen selama rendering, React membuang JSX yang dikembalikan dan segera mencoba lagi *rendering*. Untuk menghindari percobaan ulang berjenjang yang sangat lambat, React hanya mengizinkan Anda memperbarui *state* komponen *sama* selama render. Jika Anda memperbarui *state* komponen lain selama render, Anda akan melihat *error*. Kondisi seperti `items !== prevItems` diperlukan untuk menghindari perulangan. Anda dapat menyesuaikan *state* seperti ini, namun efek samping lainnya (seperti mengubah DOM atau menyetel batas waktu) harus tetap berada di *event handlers* atau *Effect* untuk [menjaga komponen tetap murni.](/learn/keeping-components-pure) -**Although this pattern is more efficient than an Effect, most components shouldn't need it either.** No matter how you do it, adjusting state based on props or other state makes your data flow more difficult to understand and debug. Always check whether you can [reset all state with a key](#resetting-all-state-when-a-prop-changes) or [calculate everything during rendering](#updating-state-based-on-props-or-state) instead. For example, instead of storing (and resetting) the selected *item*, you can store the selected *item ID:* +**Meskipun pola ini lebih efisien daripada *Effect*, sebagian besar komponen juga tidak memerlukannya.** Bagaimana pun Anda melakukannya, menyesuaikan *state* berdasarkan *props* atau *state* lainnya akan membuat aliran data Anda lebih sulit untuk dipahami dan di-*debug*. Selalu periksa apakah Anda dapat [mengatur ulang semua *state* dengan *key*](#resetting-all-state-when-a-prop-changes) atau [menghitung semuanya selama rendering](#update-state-based-on-props-or-state) sebagai gantinya. Misalnya, alih-alih menyimpan (dan mengatur ulang) *item* yang dipilih, Anda dapat menyimpan *ID item* yang dipilih: ```js {3-5} function List({ items }) { const [isReverse, setIsReverse] = useState(false); const [selectedId, setSelectedId] = useState(null); - // βœ… Best: Calculate everything during rendering + // βœ… Cara terbaik: Menghitung semuanya saat rendering const selection = items.find(item => item.id === selectedId) ?? null; // ... } ``` -Now there is no need to "adjust" the state at all. If the item with the selected ID is in the list, it remains selected. If it's not, the `selection` calculated during rendering will be `null` because no matching item was found. This behavior is different, but arguably better because most changes to `items` preserve the selection. +Sekarang tidak perlu lagi "menyesuaikan" *state*. Jika item dengan ID yang dipilih ada dalam daftar, maka item tersebut tetap dipilih. Jika tidak, `selection` yang dihitung selama *rendering* akan menjadi `null` karena tidak ditemukan item yang cocok. Perilaku ini berbeda, namun bisa dibilang lebih baik karena sebagian besar perubahan pada `item` mempertahankan pilihan. -### Sharing logic between event handlers {/*sharing-logic-between-event-handlers*/} +### Berbagi logika antar *event handler* {/*sharing-logic-between-event-handlers*/} -Let's say you have a product page with two buttons (Buy and Checkout) that both let you buy that product. You want to show a notification whenever the user puts the product in the cart. Calling `showNotification()` in both buttons' click handlers feels repetitive so you might be tempted to place this logic in an Effect: +Katakanlah Anda memiliki halaman produk dengan dua tombol (Beli dan Checkout) yang memungkinkan Anda membeli produk tersebut. Anda ingin menampilkan notifikasi setiap kali pengguna memasukkan produk ke keranjang. Memanggil `showNotification()` pada *handler* klik kedua tombol terasa berulang sehingga Anda mungkin tergoda untuk menempatkan logika ini dalam sebuah *Effect*: ```js {2-7} function ProductPage({ product, addToCart }) { - // πŸ”΄ Avoid: Event-specific logic inside an Effect + // πŸ”΄ Hindari: Logika khusus Event di dalam Effect useEffect(() => { if (product.isInCart) { showNotification(`Added ${product.name} to the shopping cart!`); @@ -277,13 +277,13 @@ function ProductPage({ product, addToCart }) { } ``` -This Effect is unnecessary. It will also most likely cause bugs. For example, let's say that your app "remembers" the shopping cart between the page reloads. If you add a product to the cart once and refresh the page, the notification will appear again. It will keep appearing every time you refresh that product's page. This is because `product.isInCart` will already be `true` on the page load, so the Effect above will call `showNotification()`. +*Effect* ini tidak diperlukan. Kemungkinan besar juga akan menyebabkan bug. Misalnya, aplikasi Anda "mengingat" keranjang belanja di antara halaman yang dimuat ulang. Jika Anda menambahkan produk ke keranjang satu kali dan me-*refresh* halaman, notifikasi akan muncul kembali. Ini akan terus muncul setiap kali Anda me-*refresh* halaman produk tersebut. Hal ini karena `product.isInCart` sudah menjadi `true` saat halaman dimuat, sehingga *Effect* di atas akan memanggil `showNotification()`. -**When you're not sure whether some code should be in an Effect or in an event handler, ask yourself *why* this code needs to run. Use Effects only for code that should run *because* the component was displayed to the user.** In this example, the notification should appear because the user *pressed the button*, not because the page was displayed! Delete the Effect and put the shared logic into a function called from both event handlers: +**Jika Anda tidak yakin apakah beberapa kode harus berada dalam *Effect* atau dalam *event handler*, tanyakan pada diri Anda *mengapa* kode ini perlu dijalankan. Gunakan *Effect* hanya untuk kode yang harus dijalankan *karena* komponen ditampilkan kepada pengguna.** Dalam contoh ini, notifikasi akan muncul karena pengguna *menekan tombol*, bukan karena halaman ditampilkan! Hapus *Effect*-nya dan masukkan logika bersama ke dalam fungsi yang dipanggil dari kedua *event handler*: ```js {2-6,9,13} function ProductPage({ product, addToCart }) { - // βœ… Good: Event-specific logic is called from event handlers + // βœ… Baik: Logika khusus event dipanggil dari event handler function buyProduct() { addToCart(product); showNotification(`Added ${product.name} to the shopping cart!`); @@ -301,23 +301,23 @@ function ProductPage({ product, addToCart }) { } ``` -This both removes the unnecessary Effect and fixes the bug. +Hal ini menghilangkan *Effect* yang tidak perlu dan memperbaiki bug. -### Sending a POST request {/*sending-a-post-request*/} +### Mengirim *request* POST {/*sending-a-post-request*/} -This `Form` component sends two kinds of POST requests. It sends an analytics event when it mounts. When you fill in the form and click the Submit button, it will send a POST request to the `/api/register` endpoint: +Komponen `Form` ini mengirimkan dua jenis *request* POST. Ini mengirimkan *event* analitik saat dipasang. Saat Anda mengisi formulir dan mengklik tombol Kirim, *request* POST akan dikirim ke *endpoint* `/api/register`: ```js {5-8,10-16} function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); - // βœ… Good: This logic should run because the component was displayed + // βœ… Baik: Logika ini seharusnya dijalankan karena komponen ditampilkan useEffect(() => { post('/analytics/event', { eventName: 'visit_form' }); }, []); - // πŸ”΄ Avoid: Event-specific logic inside an Effect + // πŸ”΄ Hindari: Logika khusus event di dalam Effect const [jsonToSubmit, setJsonToSubmit] = useState(null); useEffect(() => { if (jsonToSubmit !== null) { @@ -333,36 +333,36 @@ function Form() { } ``` -Let's apply the same criteria as in the example before. +Mari kita terapkan kriteria yang sama seperti pada contoh sebelumnya. -The analytics POST request should remain in an Effect. This is because the _reason_ to send the analytics event is that the form was displayed. (It would fire twice in development, but [see here](/learn/synchronizing-with-effects#sending-analytics) for how to deal with that.) +*Request* POST analitik harus tetap dalam *Effect*. Ini karena _alasan_ mengirim *event* analitik adalah karena formulir telah ditampilkan. (Ini akan diaktifkan dua kali dalam pengembangan, tetapi [lihat di sini](/learn/synchronizing-with-effects#sending-analytics) untuk mengetahui cara mengatasinya.) -However, the `/api/register` POST request is not caused by the form being _displayed_. You only want to send the request at one specific moment in time: when the user presses the button. It should only ever happen _on that particular interaction_. Delete the second Effect and move that POST request into the event handler: +Namun, *request* POST `/api/register` tidak disebabkan oleh formulir yang _ditampilkan_. Anda hanya ingin mengirim permintaan pada satu waktu tertentu: saat pengguna menekan tombol. Ini seharusnya hanya terjadi _pada interaksi tertentu_. Hapus *Effect* kedua dan pindahkan *request* POST tersebut ke dalam *event handler*: ```js {12-13} function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); - // βœ… Good: This logic runs because the component was displayed + // βœ… Baik: Logika ini dijalankan karena komponen ditampilkan useEffect(() => { post('/analytics/event', { eventName: 'visit_form' }); }, []); function handleSubmit(e) { e.preventDefault(); - // βœ… Good: Event-specific logic is in the event handler + // βœ… Baik: Logika khusus event di dalam event handler post('/api/register', { firstName, lastName }); } // ... } ``` -When you choose whether to put some logic into an event handler or an Effect, the main question you need to answer is _what kind of logic_ it is from the user's perspective. If this logic is caused by a particular interaction, keep it in the event handler. If it's caused by the user _seeing_ the component on the screen, keep it in the Effect. +Saat Anda memilih apakah akan memasukkan logika ke dalam *event handler* atau *Effect*, pertanyaan utama yang perlu Anda jawab adalah _logika macam apa_ dari sudut pandang pengguna. Jika logika ini disebabkan oleh interaksi tertentu, simpan logika tersebut di *event handler*. Jika hal ini disebabkan oleh pengguna _melihat_ komponen di layar, simpan di *Effect*. -### Chains of computations {/*chains-of-computations*/} +### Rantai komputasi {/*chains-of-computations*/} -Sometimes you might feel tempted to chain Effects that each adjust a piece of state based on other state: +Terkadang Anda mungkin tergoda untuk membuat *Effect* berantai yang masing-masing menyesuaikan suatu bagian *state* berdasarkan *state* lainnya: ```js {7-29} function Game() { @@ -371,7 +371,7 @@ function Game() { const [round, setRound] = useState(1); const [isGameOver, setIsGameOver] = useState(false); - // πŸ”΄ Avoid: Chains of Effects that adjust the state solely to trigger each other + // πŸ”΄ Hindari: Rantai Effect yang menyesuaikan state hanya untuk memicu Effect satu sama lain useEffect(() => { if (card !== null && card.gold) { setGoldCardCount(c => c + 1); @@ -406,13 +406,13 @@ function Game() { // ... ``` -There are two problems with this code. +Ada dua masalah dengan kode ini. -One problem is that it is very inefficient: the component (and its children) have to re-render between each `set` call in the chain. In the example above, in the worst case (`setCard` β†’ render β†’ `setGoldCardCount` β†’ render β†’ `setRound` β†’ render β†’ `setIsGameOver` β†’ render) there are three unnecessary re-renders of the tree below. +Salah satu masalahnya adalah hal ini sangat tidak efisien: komponen (dan turunannya) harus di-*render* ulang di antara setiap panggilan `set` dalam rantai. Dalam contoh di atas, dalam kasus terburuk (`setCard` β†’ *render* β†’ `setGoldCardCount` β†’ *render* β†’ `setRound` β†’ *render* β†’ `setIsGameOver` β†’ *render*) ada tiga *rendering* ulang yang tidak diperlukan pada pohon di bawah ini. -Even if it weren't slow, as your code evolves, you will run into cases where the "chain" you wrote doesn't fit the new requirements. Imagine you are adding a way to step through the history of the game moves. You'd do it by updating each state variable to a value from the past. However, setting the `card` state to a value from the past would trigger the Effect chain again and change the data you're showing. Such code is often rigid and fragile. +Meskipun tidak lambat, seiring berkembangnya kode Anda, Anda akan menghadapi kasus di mana "rantai" yang Anda tulis tidak sesuai dengan persyaratan baru. Bayangkan Anda menambahkan cara untuk menelusuri sejarah gerakan permainan. Anda akan melakukannya dengan memperbarui setiap variabel *state* ke nilai dari masa lalu. Namun, menyetel *state* `card` ke nilai dari masa lalu akan memicu rantai *Effect* lagi dan mengubah data yang Anda tampilkan. Kode seperti ini seringkali kaku dan rapuh. -In this case, it's better to calculate what you can during rendering, and adjust the state in the event handler: +Dalam hal ini, lebih baik menghitung apa yang Anda bisa selama *rendering*, dan sesuaikan *state* di *event handler*: ```js {6-7,14-26} function Game() { @@ -420,7 +420,7 @@ function Game() { const [goldCardCount, setGoldCardCount] = useState(0); const [round, setRound] = useState(1); - // βœ… Calculate what you can during rendering + // βœ… Hitung apa yang Anda bisa saat render const isGameOver = round > 5; function handlePlaceCard(nextCard) { @@ -428,7 +428,7 @@ function Game() { throw Error('Game already ended.'); } - // βœ… Calculate all the next state in the event handler + // βœ… Hitung keseluruhan state selanjutnya dalam event handler setCard(nextCard); if (nextCard.gold) { if (goldCardCount <= 3) { @@ -446,21 +446,21 @@ function Game() { // ... ``` -This is a lot more efficient. Also, if you implement a way to view game history, now you will be able to set each state variable to a move from the past without triggering the Effect chain that adjusts every other value. If you need to reuse logic between several event handlers, you can [extract a function](#sharing-logic-between-event-handlers) and call it from those handlers. +Ini jauh lebih efisien. Selain itu, jika Anda menerapkan cara untuk melihat riwayat *game*, sekarang Anda akan dapat menyetel setiap variabel *state* ke pergerakan dari masa lalu tanpa memicu rantai *Effect* yang menyesuaikan setiap nilai lainnya. Jika Anda perlu menggunakan kembali logika antara beberapa *event handler*, Anda dapat [mengekstrak fungsi](#sharing-logic-between-event-handlers) dan memanggilnya dari *handler* tersebut. -Remember that inside event handlers, [state behaves like a snapshot.](/learn/state-as-a-snapshot) For example, even after you call `setRound(round + 1)`, the `round` variable will reflect the value at the time the user clicked the button. If you need to use the next value for calculations, define it manually like `const nextRound = round + 1`. +Ingat bahwa di dalam event handler, [*state* berperilaku seperti *snapshot*.](/learn/state-as-a-snapshot) Misalnya, bahkan setelah Anda memanggil `setRound(round + 1)`, variabel `round` akan mencerminkan nilai pada saat pengguna mengklik tombol. Jika Anda perlu menggunakan nilai berikutnya untuk penghitungan, tentukan secara manual seperti `const nextRound = round + 1`. -In some cases, you *can't* calculate the next state directly in the event handler. For example, imagine a form with multiple dropdowns where the options of the next dropdown depend on the selected value of the previous dropdown. Then, a chain of Effects is appropriate because you are synchronizing with network. +Dalam beberapa kasus, Anda *tidak dapat* menghitung *state* berikutnya secara langsung di *event handler*. Misalnya, bayangkan sebuah formulir dengan beberapa *dropdown* di mana pilihan *dropdown* berikutnya bergantung pada nilai yang dipilih dari *dropdown* sebelumnya. Kemudian, rangkaian *Effect* sesuai karena Anda melakukan sinkronisasi dengan jaringan. -### Initializing the application {/*initializing-the-application*/} +### Inisialisasi aplikasi {/*initializing-the-application*/} -Some logic should only run once when the app loads. +Beberapa logika hanya boleh dijalankan satu kali saat aplikasi dimuat. -You might be tempted to place it in an Effect in the top-level component: +Anda mungkin tergoda untuk menempatkannya di *Effect* di komponen tingkat atas: ```js {2-6} function App() { - // πŸ”΄ Avoid: Effects with logic that should only ever run once + // πŸ”΄ Hindari: Effects dengan logika yang harus dijalankan sekali saja useEffect(() => { loadDataFromLocalStorage(); checkAuthToken(); @@ -469,9 +469,9 @@ function App() { } ``` -However, you'll quickly discover that it [runs twice in development.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) This can cause issues--for example, maybe it invalidates the authentication token because the function wasn't designed to be called twice. In general, your components should be resilient to being remounted. This includes your top-level `App` component. +Namun, Anda akan segera menyadari bahwa ini [berjalan dua kali dalam pengembangan.](/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development) Hal ini dapat menyebabkan masalah-- misalnya, mungkin token autentikasinya menjadi tidak valid karena fungsinya tidak dirancang untuk dipanggil dua kali. Secara umum, komponen Anda harus tahan untuk dipasang ulang. Ini termasuk komponen `App` tingkat atas Anda. -Although it may not ever get remounted in practice in production, following the same constraints in all components makes it easier to move and reuse code. If some logic must run *once per app load* rather than *once per component mount*, add a top-level variable to track whether it has already executed: +Meskipun dalam praktiknya mungkin tidak akan pernah dipasang ulang dalam produksi, mengikuti batasan yang sama di semua komponen akan mempermudah pemindahan dan penggunaan kembali kode. Jika beberapa logika harus dijalankan *sekali per pemuatan aplikasi* dan bukan *sekali per pemasangan komponen*, tambahkan variabel tingkat atas untuk melacak apakah logika tersebut sudah dijalankan: ```js {1,5-6,10} let didInit = false; @@ -480,7 +480,7 @@ function App() { useEffect(() => { if (!didInit) { didInit = true; - // βœ… Only runs once per app load + // βœ… Hanya dijalankan sekali saat aplikasi dimuat loadDataFromLocalStorage(); checkAuthToken(); } @@ -492,8 +492,8 @@ function App() { You can also run it during module initialization and before the app renders: ```js {1,5} -if (typeof window !== 'undefined') { // Check if we're running in the browser. - // βœ… Only runs once per app load +if (typeof window !== 'undefined') { // Cek apakah kode berjalan di peramban. + // βœ… Hanya dijalankan sekali saat aplikasi dimuat checkAuthToken(); loadDataFromLocalStorage(); } @@ -503,17 +503,17 @@ function App() { } ``` -Code at the top level runs once when your component is imported--even if it doesn't end up being rendered. To avoid slowdown or surprising behavior when importing arbitrary components, don't overuse this pattern. Keep app-wide initialization logic to root component modules like `App.js` or in your application's entry point. +Kode di tingkat atas dijalankan satu kali saat komponen Anda diimpor--walaupun komponen tersebut tidak di-*render*. Untuk menghindari perlambatan atau perilaku tidak terduga saat mengimpor komponen sembarangan, jangan terlalu sering menggunakan pola ini. Pertahankan logika inisialisasi seluruh aplikasi ke root modul komponen seperti `App.js` atau di titik masuk aplikasi Anda. -### Notifying parent components about state changes {/*notifying-parent-components-about-state-changes*/} +### Memberi tahu komponen induk tentang perubahan *state* {/*notifying-parent-components-about-state-changes*/} -Let's say you're writing a `Toggle` component with an internal `isOn` state which can be either `true` or `false`. There are a few different ways to toggle it (by clicking or dragging). You want to notify the parent component whenever the `Toggle` internal state changes, so you expose an `onChange` event and call it from an Effect: +Katakanlah Anda sedang menulis komponen `Toggle` dengan *state* `isOn` internal yang dapat berupa `true` atau `false`. Ada beberapa cara berbeda untuk mengaktifkannya (dengan mengeklik atau menggeser). Anda ingin memberi tahu komponen induk setiap kali *state* internal `Toggle` berubah, sehingga Anda mengekspos *event* `onChange` dan memanggilnya dari *Effect*: ```js {4-7} function Toggle({ onChange }) { const [isOn, setIsOn] = useState(false); - // πŸ”΄ Avoid: The onChange handler runs too late + // πŸ”΄ Hindari: Event handler onChange terlambat dijalankan useEffect(() => { onChange(isOn); }, [isOn, onChange]) @@ -534,16 +534,16 @@ function Toggle({ onChange }) { } ``` -Like earlier, this is not ideal. The `Toggle` updates its state first, and React updates the screen. Then React runs the Effect, which calls the `onChange` function passed from a parent component. Now the parent component will update its own state, starting another render pass. It would be better to do everything in a single pass. +Seperti sebelumnya, ini tidak ideal. `Toggle` memperbarui *state*-nya terlebih dahulu, dan React memperbarui layar. Kemudian React menjalankan *Effect*, yang memanggil fungsi `onChange` yang diteruskan dari komponen induk. Sekarang komponen induk akan memperbarui *state*-nya sendiri, memulai proses *render* lainnya. Akan lebih baik jika melakukan semuanya dalam sekali jalan. -Delete the Effect and instead update the state of *both* components within the same event handler: +Hapus *Effect*-nya dan perbarui status *kedua* komponen dalam *event handler* yang sama: ```js {5-7,11,16,18} function Toggle({ onChange }) { const [isOn, setIsOn] = useState(false); function updateToggle(nextIsOn) { - // βœ… Good: Perform all updates during the event that caused them + // βœ… Baik: Lakukan semua pembaruan selama event yang menyebabkannya setIsOn(nextIsOn); onChange(nextIsOn); } @@ -564,12 +564,12 @@ function Toggle({ onChange }) { } ``` -With this approach, both the `Toggle` component and its parent component update their state during the event. React [batches updates](/learn/queueing-a-series-of-state-updates) from different components together, so there will only be one render pass. +Dengan pendekatan ini, komponen `Toggle` dan komponen induknya memperbarui *state*-nya selama *event*. React [mem-*batch* pembaruan](/learn/queueing-a-series-of-state-updates) dari berbagai komponen secara bersamaan, sehingga hanya akan ada satu *render pass*. -You might also be able to remove the state altogether, and instead receive `isOn` from the parent component: +Anda mungkin juga dapat menghapus *state* tersebut, dan sebagai gantinya menerima `isOn` dari komponen induk: ```js {1,2} -// βœ… Also good: the component is fully controlled by its parent +// βœ… Juga baik: komponen terkontrol secara penuh oleh induknya function Toggle({ isOn, onChange }) { function handleClick() { onChange(!isOn); @@ -587,11 +587,11 @@ function Toggle({ isOn, onChange }) { } ``` -["Lifting state up"](/learn/sharing-state-between-components) lets the parent component fully control the `Toggle` by toggling the parent's own state. This means the parent component will have to contain more logic, but there will be less state overall to worry about. Whenever you try to keep two different state variables synchronized, try lifting state up instead! +["Menaikkan *state* ke atas"](/learn/sharing-state-between-components) memungkinkan komponen induk mengontrol sepenuhnya `Toggle` dengan mengubah *state* induknya sendiri. Ini berarti komponen induk harus mengandung lebih banyak logika, namun secara keseluruhan akan ada lebih sedikit *state* yang perlu dikhawatirkan. Setiap kali Anda mencoba untuk menyinkronkan dua variabel *state* yang berbeda, coba naikkan *state*! -### Passing data to the parent {/*passing-data-to-the-parent*/} +### Mengoper data ke komponen induk {/*passing-data-to-the-parent*/} -This `Child` component fetches some data and then passes it to the `Parent` component in an Effect: +Komponen `Child` ini mengambil data dan kemudian meneruskannya ke komponen `Parent` dalam sebuah *Effect*: ```js {9-14} function Parent() { @@ -602,7 +602,7 @@ function Parent() { function Child({ onFetched }) { const data = useSomeAPI(); - // πŸ”΄ Avoid: Passing data to the parent in an Effect + // πŸ”΄ Hindari: Mengoper data ke komponen induk di dalam Effect useEffect(() => { if (data) { onFetched(data); @@ -612,13 +612,13 @@ function Child({ onFetched }) { } ``` -In React, data flows from the parent components to their children. When you see something wrong on the screen, you can trace where the information comes from by going up the component chain until you find which component passes the wrong prop or has the wrong state. When child components update the state of their parent components in Effects, the data flow becomes very difficult to trace. Since both the child and the parent need the same data, let the parent component fetch that data, and *pass it down* to the child instead: +Di React, data mengalir dari komponen induk ke komponen turunannya. Saat Anda melihat sesuatu yang salah di layar, Anda dapat melacak dari mana informasi tersebut berasal dengan menelusuri rantai komponen ke atas hingga Anda menemukan komponen mana yang mengoper *props* yang salah atau memiliki *state* yang salah. Saat komponen anak memperbarui *state* komponen induknya di *Effect*, aliran data menjadi sangat sulit dilacak. Karena komponen turunan dan induk membutuhkan data yang sama, biarkan komponen induk mengambil data tersebut, dan *meneruskannya* ke turunan: ```js {4-5} function Parent() { const data = useSomeAPI(); // ... - // βœ… Good: Passing data down to the child + // βœ… Baik: Meneruskan data ke komponen anak return ; } @@ -627,15 +627,15 @@ function Child({ data }) { } ``` -This is simpler and keeps the data flow predictable: the data flows down from the parent to the child. +Hal ini lebih simpel dan menjaga aliran data tetap dapat diprediksi: data mengalir dari induk ke turunan. -### Subscribing to an external store {/*subscribing-to-an-external-store*/} +### Berlangganan ke penyimpanan data eksternal {/*subscribing-to-an-external-store*/} -Sometimes, your components may need to subscribe to some data outside of the React state. This data could be from a third-party library or a built-in browser API. Since this data can change without React's knowledge, you need to manually subscribe your components to it. This is often done with an Effect, for example: +Terkadang, komponen Anda mungkin perlu berlangganan beberapa data di luar *state* React. Data ini bisa berasal dari pustaka pihak ketiga atau API browser bawaan. Karena data ini dapat berubah tanpa sepengetahuan React, Anda perlu berlangganan komponen Anda secara manual. Hal ini sering dilakukan dengan *Effect*, misalnya: ```js {2-17} function useOnlineStatus() { - // Not ideal: Manual store subscription in an Effect + // Tidak ideal: Berlangganan store secara manual dalam Effect const [isOnline, setIsOnline] = useState(true); useEffect(() => { function updateState() { @@ -660,9 +660,9 @@ function ChatIndicator() { } ``` -Here, the component subscribes to an external data store (in this case, the browser `navigator.onLine` API). Since this API does not exist on the server (so it can't be used for the initial HTML), initially the state is set to `true`. Whenever the value of that data store changes in the browser, the component updates its state. +Di sini, komponen berlangganan penyimpanan data eksternal (dalam hal ini, API `navigator.onLine` browser). Karena API ini tidak ada di server (sehingga tidak dapat digunakan untuk HTML awal), awalnya *state* disetel ke `true`. Setiap kali nilai penyimpanan data tersebut berubah di browser, komponen akan memperbarui *state*-nya. -Although it's common to use Effects for this, React has a purpose-built Hook for subscribing to an external store that is preferred instead. Delete the Effect and replace it with a call to [`useSyncExternalStore`](/reference/react/useSyncExternalStore): +Meskipun *Effect* umumnya digunakan untuk hal ini, React memiliki Hook yang dibuat khusus untuk berlangganan penyimpanan data eksternal yang lebih disarankan digunakan. Hapus *state* dan ganti dengan panggilan ke [`useSyncExternalStore`](/reference/react/useSyncExternalStore): ```js {11-16} function subscribe(callback) { @@ -675,11 +675,11 @@ function subscribe(callback) { } function useOnlineStatus() { - // βœ… Good: Subscribing to an external store with a built-in Hook + // βœ… Baik: Berlangganan store eksternal dengan Hook bawaan return useSyncExternalStore( - subscribe, // React won't resubscribe for as long as you pass the same function - () => navigator.onLine, // How to get the value on the client - () => true // How to get the value on the server + subscribe, // React tidak akan berlangganan ulang selama Anda mengoper fungsi yang sama + () => navigator.onLine, // Bagaimana cara mendapatkan nilai client + () => true // Bagaimana cara mendapatkan nilai server ); } @@ -689,11 +689,11 @@ function ChatIndicator() { } ``` -This approach is less error-prone than manually syncing mutable data to React state with an Effect. Typically, you'll write a custom Hook like `useOnlineStatus()` above so that you don't need to repeat this code in the individual components. [Read more about subscribing to external stores from React components.](/reference/react/useSyncExternalStore) +Pendekatan ini lebih tidak rentan terhadap kesalahan dibandingkan menyinkronkan secara manual data yang dapat diubah ke *state* React dengan *Effec*. Biasanya, Anda akan menulis Hook khusus seperti `useOnlineStatus()` di atas sehingga Anda tidak perlu mengulangi kode ini di masing-masing komponen. [Baca selengkapnya tentang berlangganan penyimpanan data eksternal dari komponen React.](/reference/react/useSyncExternalStore) -### Fetching data {/*fetching-data*/} +### Mengambil data {/*fetching-data*/} -Many apps use Effects to kick off data fetching. It is quite common to write a data fetching Effect like this: +Banyak aplikasi menggunakan *Effect* untuk memulai pengambilan data. Sangat umum untuk menulis *Effect* pengambilan data seperti ini: ```js {5-10} function SearchResults({ query }) { @@ -701,7 +701,7 @@ function SearchResults({ query }) { const [page, setPage] = useState(1); useEffect(() => { - // πŸ”΄ Avoid: Fetching without cleanup logic + // πŸ”΄ Hindari: Pengambilan data tanpa logika pembersihan fetchResults(query, page).then(json => { setResults(json); }); @@ -714,15 +714,15 @@ function SearchResults({ query }) { } ``` -You *don't* need to move this fetch to an event handler. +Anda *tidak* perlu memindahkan pengambilan ini ke *event handler*. -This might seem like a contradiction with the earlier examples where you needed to put the logic into the event handlers! However, consider that it's not *the typing event* that's the main reason to fetch. Search inputs are often prepopulated from the URL, and the user might navigate Back and Forward without touching the input. +Ini mungkin tampak seperti kontradiksi dengan contoh sebelumnya di mana Anda perlu memasukkan logika ke dalam *event handler*! Namun, pertimbangkan bahwa bukan *event pengetikan* yang menjadi alasan utama pengambilan. *Input* penelusuran sering kali diisi sebelumnya dari URL, dan pengguna dapat menavigasi Mundur dan Maju tanpa menyentuh *input* tersebut. -It doesn't matter where `page` and `query` come from. While this component is visible, you want to keep `results` [synchronized](/learn/synchronizing-with-effects) with data from the network for the current `page` and `query`. This is why it's an Effect. +Tidak masalah dari mana `page` dan `query` berasal. Saat komponen ini terlihat, Anda ingin tetap `results` [disinkronkan](/learn/synchronizing-with-effects) dengan data dari jaringan untuk `page` dan `query` saat ini. Inilah mengapa ini merupakan *Effect*. -However, the code above has a bug. Imagine you type `"hello"` fast. Then the `query` will change from `"h"`, to `"he"`, `"hel"`, `"hell"`, and `"hello"`. This will kick off separate fetches, but there is no guarantee about which order the responses will arrive in. For example, the `"hell"` response may arrive *after* the `"hello"` response. Since it will call `setResults()` last, you will be displaying the wrong search results. This is called a ["race condition"](https://en.wikipedia.org/wiki/Race_condition): two different requests "raced" against each other and came in a different order than you expected. +Namun kode diatas mempunyai bug. Bayangkan Anda mengetik `"hello"` dengan cepat. Kemudian `query` akan berubah dari `"h"`, menjadi `"he"`, `"hel"`, `"hell"`, dan `"hello"`. Ini akan memulai pengambilan terpisah, namun tidak ada jaminan urutan respons yang akan diterima. Misalnya, respons `"hell"` mungkin muncul *setelah* respons `"hello"`. Karena ini akan memanggil `setResults()` terakhir, Anda akan menampilkan hasil pencarian yang salah. Ini disebut ["*race condition*"](https://en.wikipedia.org/wiki/Race_condition): dua permintaan berbeda "berlomba" satu sama lain dan datang dalam urutan berbeda dari yang Anda harapkan. -**To fix the race condition, you need to [add a cleanup function](/learn/synchronizing-with-effects#fetching-data) to ignore stale responses:** +**Untuk memperbaiki *race condition*, anda perlu [menambahkan fungsi pembersihan](/learn/synchronizing-with-effects#fetching-data) untuk mengabaikan respons yang sudah usang:** ```js {5,7,9,11-13} function SearchResults({ query }) { @@ -747,13 +747,14 @@ function SearchResults({ query }) { } ``` -This ensures that when your Effect fetches data, all responses except the last requested one will be ignored. -Handling race conditions is not the only difficulty with implementing data fetching. You might also want to think about caching responses (so that the user can click Back and see the previous screen instantly), how to fetch data on the server (so that the initial server-rendered HTML contains the fetched content instead of a spinner), and how to avoid network waterfalls (so that a child can fetch data without waiting for every parent). +Hal ini memastikan bahwa saat *Effect* Anda mengambil data, semua respons kecuali yang terakhir diminta akan diabaikan. -**These issues apply to any UI library, not just React. Solving them is not trivial, which is why modern [frameworks](/learn/start-a-new-react-project#production-grade-react-frameworks) provide more efficient built-in data fetching mechanisms than fetching data in Effects.** +Menangani *race condition* bukan satu-satunya kesulitan dalam mengimplementasikan pengambilan data. Anda mungkin juga ingin memikirkan tentang *cache* terhadap respons (sehingga pengguna dapat mengeklik Kembali dan langsung melihat layar sebelumnya), cara mengambil data di server (sehingga HTML awal yang di-*render* oleh server berisi konten yang diambil, bukan *spinner*), dan cara menghindari air terjun jaringan (*network waterfalls*) (sehingga komponen anak dapat mengambil data tanpa menunggu induknya). -If you don't use a framework (and don't want to build your own) but would like to make data fetching from Effects more ergonomic, consider extracting your fetching logic into a custom Hook like in this example: +**Masalah ini berlaku untuk semua perpustakaan UI, bukan hanya React. Menyelesaikannya bukanlah hal yang sepele, itulah sebabnya [kerangka kerja](/learn/start-a-new-react-project#production-grade-react-frameworks) modern menyediakan mekanisme pengambilan data bawaan yang lebih efisien daripada mengambil data di *Effect*.** + +Jika Anda tidak menggunakan kerangka kerja (dan tidak ingin membuat kerangka kerja sendiri) namun ingin membuat pengambilan data dari *Effect* lebih ergonomis, pertimbangkan untuk mengekstrak logika pengambilan Anda ke dalam Hook khusus seperti dalam contoh ini: ```js {4} function SearchResults({ query }) { @@ -786,30 +787,31 @@ function useData(url) { } ``` -You'll likely also want to add some logic for error handling and to track whether the content is loading. You can build a Hook like this yourself or use one of the many solutions already available in the React ecosystem. **Although this alone won't be as efficient as using a framework's built-in data fetching mechanism, moving the data fetching logic into a custom Hook will make it easier to adopt an efficient data fetching strategy later.** -In general, whenever you have to resort to writing Effects, keep an eye out for when you can extract a piece of functionality into a custom Hook with a more declarative and purpose-built API like `useData` above. The fewer raw `useEffect` calls you have in your components, the easier you will find to maintain your application. +Anda mungkin juga ingin menambahkan beberapa logika untuk penanganan *error* dan melacak apakah konten sedang dimuat. Anda dapat membuat Hook seperti ini sendiri atau menggunakan salah satu dari banyak solusi yang sudah tersedia di ekosistem React. **Meskipun hal ini saja tidak akan seefisien menggunakan mekanisme pengambilan data bawaan kerangka kerja, memindahkan logika pengambilan data ke dalam Hook kustom akan mempermudah penerapan strategi pengambilan data yang efisien nantinya.** + +Secara umum, kapan pun Anda harus menulis *Effect*, perhatikan kapan Anda dapat mengekstrak sebagian fungsionalitas ke dalam Hook khusus dengan API yang lebih deklaratif dan dibuat khusus seperti `useData` di atas. Semakin sedikit panggilan `useEffect` mentah yang Anda miliki di komponen Anda, semakin mudah Anda memelihara aplikasi Anda. -- If you can calculate something during render, you don't need an Effect. -- To cache expensive calculations, add `useMemo` instead of `useEffect`. -- To reset the state of an entire component tree, pass a different `key` to it. -- To reset a particular bit of state in response to a prop change, set it during rendering. -- Code that runs because a component was *displayed* should be in Effects, the rest should be in events. -- If you need to update the state of several components, it's better to do it during a single event. -- Whenever you try to synchronize state variables in different components, consider lifting state up. -- You can fetch data with Effects, but you need to implement cleanup to avoid race conditions. +- Apabila Anda dapat menghitung nilai sesuatu saat *render*, Anda tidak memerlukan Effect. +- Untuk meng-*cache* penghitungan yang mahal, tambahkan `useMemo` sebagai ganti `useEffect`. +- Untuk menyetel ulang *state* sebuah pohon komponen secara keseluruhan, berikan `key` yang berbeda ke dalamnya. +- Untuk mengatur ulang *state* tertentu sebagai respons terhadap perubahan *props*, aturlah selama *rendering*. +- Kode yang berjalan karena komponen *ditampilkan* harus di *Effect*, sisanya harus di *event*. +- Jika Anda perlu memperbarui *state* beberapa komponen, lebih baik melakukannya dalam satu *event*. +- Setiap kali Anda mencoba menyinkronkan variabel *state* di komponen yang berbeda, pertimbangkan untuk menaikkan *state*. +- Anda dapat mengambil data dengan *Effect*, tetapi Anda perlu menerapkan pembersihan untuk menghindari *race condition*. -#### Transform data without Effects {/*transform-data-without-effects*/} +#### Mentransformasi data tanpa *Effect* {/*transform-data-without-effects*/} -The `TodoList` below displays a list of todos. When the "Show only active todos" checkbox is ticked, completed todos are not displayed in the list. Regardless of which todos are visible, the footer displays the count of todos that are not yet completed. +Komponen `TodoList` di bawah menampilkan daftar tugas. Jika kotak centang "Tampilkan hanya tugas yang aktif" dicentang, tugas yang sudah selesai tidak akan ditampilkan dalam daftar. Terlepas dari tugas mana yang terlihat, *footer* menampilkan jumlah tugas yang belum selesai. -Simplify this component by removing all the unnecessary state and Effects. +Sederhanakan komponen ini dengan menghapus semua *state* dan *Effect* yang tidak perlu. @@ -835,7 +837,7 @@ export default function TodoList() { useEffect(() => { setFooter(
- {activeTodos.length} todos left + Sisa {activeTodos.length} tugas
); }, [activeTodos]); @@ -848,7 +850,7 @@ export default function TodoList() { checked={showActive} onChange={e => setShowActive(e.target.checked)} /> - Show only active todos + Tampilkan hanya tugas yang aktif setTodos([...todos, newTodo])} />
    @@ -875,7 +877,7 @@ function NewTodo({ onAdd }) { <> setText(e.target.value)} /> ); @@ -894,9 +896,9 @@ export function createTodo(text, completed = false) { } export const initialTodos = [ - createTodo('Get apples', true), - createTodo('Get oranges', true), - createTodo('Get carrots'), + createTodo('Beli apel', true), + createTodo('Beli jeruk', true), + createTodo('Beli wortel'), ]; ``` @@ -909,15 +911,15 @@ input { margin-top: 10px; } -If you can calculate something during rendering, you don't need state or an Effect that updates it. +Jika Anda dapat menghitung nilai dari sesuatu saat *rendering*, Anda tidak memerlukan *state* atau *Effect* yang memperbaruinya. -There are only two essential pieces of state in this example: the list of `todos` and the `showActive` state variable which represents whether the checkbox is ticked. All of the other state variables are [redundant](/learn/choosing-the-state-structure#avoid-redundant-state) and can be calculated during rendering instead. This includes the `footer` which you can move directly into the surrounding JSX. +Hanya ada dua bagian penting dari *state* dalam contoh ini: daftar `todos` dan variabel *state* `showActive` yang menunjukkan apakah kotak centang dicentang. Semua variabel *state* lainnya adalah [redundan](/learn/choosing-the-state-structure#avoid-redundant-state) dan dapat dihitung selama *rendering*. Ini termasuk `footer` yang dapat Anda pindahkan langsung ke JSX di sekitarnya. -Your result should end up looking like this: +Hasil Anda akan terlihat seperti ini: @@ -939,7 +941,7 @@ export default function TodoList() { checked={showActive} onChange={e => setShowActive(e.target.checked)} /> - Show only active todos + Tampilkan hanya tugas yang aktif setTodos([...todos, newTodo])} />
      @@ -950,7 +952,7 @@ export default function TodoList() { ))}
    - {activeTodos.length} todos left + Sisa {activeTodos.length} tugas
    ); @@ -968,7 +970,7 @@ function NewTodo({ onAdd }) { <> setText(e.target.value)} /> ); @@ -987,9 +989,9 @@ export function createTodo(text, completed = false) { } export const initialTodos = [ - createTodo('Get apples', true), - createTodo('Get oranges', true), - createTodo('Get carrots'), + createTodo('Beli apel', true), + createTodo('Beli jeruk', true), + createTodo('Beli wortel'), ]; ``` @@ -1002,15 +1004,15 @@ input { margin-top: 10px; }
    -#### Cache a calculation without Effects {/*cache-a-calculation-without-effects*/} +#### Meng-*cache* perhitungan tanpa *Effect* {/*cache-a-calculation-without-effects*/} -In this example, filtering the todos was extracted into a separate function called `getVisibleTodos()`. This function contains a `console.log()` call inside of it which helps you notice when it's being called. Toggle "Show only active todos" and notice that it causes `getVisibleTodos()` to re-run. This is expected because visible todos change when you toggle which ones to display. +Dalam contoh ini, pemfilteran daftar tugas diekstrak ke dalam fungsi terpisah yang disebut `getVisibleTodos()`. Fungsi ini berisi panggilan `console.log()` di dalamnya yang membantu Anda mengetahui saat fungsi tersebut dipanggil. Alihkan "Tampilkan hanya tugas yang aktif" dan perhatikan bahwa fungsi tersebut menyebabkan `getVisibleTodos()` dijalankan ulang. Hal ini diharapkan karena tugas yang terlihat berubah saat Anda mengubah tugas mana yang akan ditampilkan. -Your task is to remove the Effect that recomputes the `visibleTodos` list in the `TodoList` component. However, you need to make sure that `getVisibleTodos()` does *not* re-run (and so does not print any logs) when you type into the input. +Tugas Anda adalah menghapus *Effect* yang menghitung ulang daftar `visibleTodos` dalam komponen `TodoList`. Namun, Anda perlu memastikan bahwa `getVisibleTodos()` *tidak* dijalankan ulang (dan karenanya tidak mencetak log apa pun) saat Anda mengetik ke dalam input. -One solution is to add a `useMemo` call to cache the visible todos. There is also another, less obvious solution. +Salah satu solusinya adalah menambahkan panggilan `useMemo` untuk menyimpan *cache* dari tugas yang terlihat. Ada juga solusi lain yang tidak terlalu jelas. @@ -1043,11 +1045,11 @@ export default function TodoList() { checked={showActive} onChange={e => setShowActive(e.target.checked)} /> - Show only active todos + Tampilkan hanya tugas yang aktif setText(e.target.value)} />
      {visibleTodos.map(todo => ( @@ -1081,9 +1083,9 @@ export function createTodo(text, completed = false) { } export const initialTodos = [ - createTodo('Get apples', true), - createTodo('Get oranges', true), - createTodo('Get carrots'), + createTodo('Beli apel', true), + createTodo('Beli jeruk', true), + createTodo('Beli wortel'), ]; ``` @@ -1096,7 +1098,7 @@ input { margin-top: 10px; } -Remove the state variable and the Effect, and instead add a `useMemo` call to cache the result of calling `getVisibleTodos()`: +Hapus variabel *state* dan *Effect*, dan sebagai gantinya tambahkan panggilan `useMemo` untuk menyimpan hasil pemanggilan `getVisibleTodos()`: @@ -1126,11 +1128,11 @@ export default function TodoList() { checked={showActive} onChange={e => setShowActive(e.target.checked)} /> - Show only active todos + Tampilkan hanya tugas yang aktif setText(e.target.value)} />
        {visibleTodos.map(todo => ( @@ -1164,9 +1166,9 @@ export function createTodo(text, completed = false) { } export const initialTodos = [ - createTodo('Get apples', true), - createTodo('Get oranges', true), - createTodo('Get carrots'), + createTodo('Beli apel', true), + createTodo('Beli jeruk', true), + createTodo('Beli wortel'), ]; ``` @@ -1177,9 +1179,9 @@ input { margin-top: 10px; } -With this change, `getVisibleTodos()` will be called only if `todos` or `showActive` change. Typing into the input only changes the `text` state variable, so it does not trigger a call to `getVisibleTodos()`. +Dengan perubahan ini, `getVisibleTodos()` hanya akan dipanggil jika `todos` atau `showActive` berubah. Mengetik ke input hanya mengubah variabel *state* `text`, jadi tidak memicu panggilan ke `getVisibleTodos()`. -There is also another solution which does not need `useMemo`. Since the `text` state variable can't possibly affect the list of todos, you can extract the `NewTodo` form into a separate component, and move the `text` state variable inside of it: +Ada juga solusi lain yang tidak memerlukan `useMemo`. Karena variabel *state* `text` tidak mungkin memengaruhi daftar todo, Anda dapat mengekstrak formulir `NewTodo` ke dalam komponen terpisah, dan memindahkan variabel *state* `text` ke dalamnya: @@ -1200,7 +1202,7 @@ export default function TodoList() { checked={showActive} onChange={e => setShowActive(e.target.checked)} /> - Show only active todos + Tampilkan hanya tugas yang aktif setTodos([...todos, newTodo])} />
          @@ -1226,7 +1228,7 @@ function NewTodo({ onAdd }) { <> setText(e.target.value)} /> ); @@ -1253,9 +1255,9 @@ export function createTodo(text, completed = false) { } export const initialTodos = [ - createTodo('Get apples', true), - createTodo('Get oranges', true), - createTodo('Get carrots'), + createTodo('Beli apel', true), + createTodo('Beli jeruk', true), + createTodo('Beli wortel'), ]; ``` @@ -1266,15 +1268,15 @@ input { margin-top: 10px; } -This approach satisfies the requirements too. When you type into the input, only the `text` state variable updates. Since the `text` state variable is in the child `NewTodo` component, the parent `TodoList` component won't get re-rendered. This is why `getVisibleTodos()` doesn't get called when you type. (It would still be called if the `TodoList` re-renders for another reason.) +Pendekatan ini juga memenuhi persyaratan. Saat Anda mengetik ke dalam input, hanya variabel *state* `text` yang diperbarui. Karena variabel *state* `text` ada di komponen anak `NewTodo`, komponen induk `TodoList` tidak akan di-*render* ulang. Inilah sebabnya `getVisibleTodos()` tidak dipanggil saat Anda mengetik. (Itu akan tetap dipanggil jika `TodoList` di-*render* ulang karena alasan lain.) -#### Reset state without Effects {/*reset-state-without-effects*/} +#### Menyetel ulang *state* tanpa *Effect* {/*reset-state-without-effects*/} -This `EditContact` component receives a contact object shaped like `{ id, name, email }` as the `savedContact` prop. Try editing the name and email input fields. When you press Save, the contact's button above the form updates to the edited name. When you press Reset, any pending changes in the form are discarded. Play around with this UI to get a feel for it. +Komponen `EditContact` ini menerima objek kontak berbentuk seperti `{ id, name, email }` sebagai *props* `savedContact`. Coba mengubah kolom input nama dan email. Saat Anda menekan Simpan, tombol kontak di atas formulir akan diperbarui ke nama yang diedit. Saat Anda menekan Atur Ulang, semua perubahan yang tertunda dalam formulir akan dibuang. Cobalah UI ini untuk merasakannya. -When you select a contact with the buttons at the top, the form resets to reflect that contact's details. This is done with an Effect inside `EditContact.js`. Remove this Effect. Find another way to reset the form when `savedContact.id` changes. +Saat Anda memilih kontak dengan tombol di bagian atas, formulir akan disetel ulang untuk mencerminkan detail kontak tersebut. Ini dilakukan dengan *Effect* di dalam `EditContact.js`. Hapus *Effect* ini. Temukan cara lain untuk menyetel ulang formulir saat `savedContact.id` berubah. @@ -1372,7 +1374,7 @@ export default function EditContact({ savedContact, onSave }) { return (
          ); @@ -1432,13 +1434,13 @@ button { -It would be nice if there was a way to tell React that when `savedContact.id` is different, the `EditContact` form is conceptually a _different contact's form_ and should not preserve state. Do you recall any such way? +Akan lebih baik jika ada cara untuk memberi tahu React bahwa ketika `savedContact.id` berbeda, formulir `EditContact` secara konseptual adalah _formulir kontak yang berbeda_ dan tidak boleh mempertahankan *state*. Apakah Anda ingat cara seperti itu? -Split the `EditContact` component in two. Move all the form state into the inner `EditForm` component. Export the outer `EditContact` component, and make it pass `savedContact.id` as the `key` to the inner `EditForm` component. As a result, the inner `EditForm` component resets all of the form state and recreates the DOM whenever you select a different contact. +Pisahkan komponen `EditContact` menjadi dua. Pindahkan semua *state* formulir ke komponen `EditForm` bagian dalam. Ekspor komponen `EditContact` bagian luar, dan buat komponen tersebut meneruskan `savedContact.id` sebagai `key` ke komponen `EditForm` bagian dalam. Hasilnya, komponen `EditForm` bagian dalam akan menyetel ulang semua *state* formulir dan membuat ulang DOM setiap kali Anda memilih kontak yang berbeda. @@ -1540,7 +1542,7 @@ function EditForm({ savedContact, onSave }) { return (
          ); @@ -1600,17 +1602,17 @@ button {
          -#### Submit a form without Effects {/*submit-a-form-without-effects*/} +#### Mengirim formulir tanpa *Effect* {/*submit-a-form-without-effects*/} -This `Form` component lets you send a message to a friend. When you submit the form, the `showForm` state variable is set to `false`. This triggers an Effect calling `sendMessage(message)`, which sends the message (you can see it in the console). After the message is sent, you see a "Thank you" dialog with an "Open chat" button that lets you get back to the form. +Komponen `Form` ini memungkinkan Anda mengirim pesan ke teman. Saat Anda mengirimkan formulir, variabel *state* `showForm` disetel ke `false`. Ini memicu *Effect* yang memanggil `sendMessage(message)`, yang mengirim pesan (Anda dapat melihatnya di konsol). Setelah pesan terkirim, Anda akan melihat dialog "Terima kasih" dengan tombol "Buka obrolan" yang memungkinkan Anda kembali ke formulir. -Your app's users are sending way too many messages. To make chatting a little bit more difficult, you've decided to show the "Thank you" dialog *first* rather than the form. Change the `showForm` state variable to initialize to `false` instead of `true`. As soon as you make that change, the console will show that an empty message was sent. Something in this logic is wrong! +Pengguna aplikasi Anda mengirim terlalu banyak pesan. Untuk membuat obrolan sedikit lebih sulit, Anda telah memutuskan untuk menampilkan dialog "Terima kasih" *terlebih dahulu* daripada formulir. Ubah variabel *state* `showForm` untuk diinisialisasi ke `false` alih-alih `true`. Segera setelah Anda membuat perubahan itu, konsol akan menunjukkan bahwa pesan kosong telah dikirim. Ada yang salah dalam logika ini! -What's the root cause of this problem? And how can you fix it? +Apa akar penyebab masalah ini? Dan bagaimana cara memperbaikinya? -Should the message be sent _because_ the user saw the "Thank you" dialog? Or is it the other way around? +Haruskah pesan dikirim _karena_ pengguna melihat dialog "Terima kasih"? Atau sebaliknya? @@ -1637,12 +1639,12 @@ export default function Form() { if (!showForm) { return ( <> -

          Thanks for using our services!

          +

          Terima kasih telah menggunakan layanan kami!

          ); @@ -1656,14 +1658,14 @@ export default function Form() { onChange={e => setMessage(e.target.value)} /> ); } function sendMessage(message) { - console.log('Sending message: ' + message); + console.log('Mengirim pesan: ' + message); } ``` @@ -1675,7 +1677,7 @@ label, textarea { margin-bottom: 10px; display: block; } -The `showForm` state variable determines whether to show the form or the "Thank you" dialog. However, you aren't sending the message because the "Thank you" dialog was _displayed_. You want to send the message because the user has _submitted the form._ Delete the misleading Effect and move the `sendMessage` call inside the `handleSubmit` event handler: +Variabel *state* `showForm` menentukan apakah akan menampilkan formulir atau dialog "Terima kasih". Namun, Anda tidak mengirim pesan karena dialog "Terima kasih" _ditampilkan_. Anda ingin mengirim pesan karena pengguna telah _mengirimkan formulir._ Hapus *Effect* yang menyesatkan dan pindahkan panggilan `sendMessage` ke dalam *event handler* `handleSubmit`: @@ -1695,12 +1697,12 @@ export default function Form() { if (!showForm) { return ( <> -

          Thanks for using our services!

          +

          Terima kasih telah menggunakan layanan kami!

          ); @@ -1714,14 +1716,14 @@ export default function Form() { onChange={e => setMessage(e.target.value)} /> ); } function sendMessage(message) { - console.log('Sending message: ' + message); + console.log('Mengirim pesan: ' + message); } ``` @@ -1731,7 +1733,7 @@ label, textarea { margin-bottom: 10px; display: block; }
          -Notice how in this version, only _submitting the form_ (which is an event) causes the message to be sent. It works equally well regardless of whether `showForm` is initially set to `true` or `false`. (Set it to `false` and notice no extra console messages.) +Perhatikan bagaimana dalam versi ini, hanya _mengirimkan formulir_ (yang merupakan suatu *event*) yang menyebabkan pesan terkirim. Ini berfungsi sama baiknya terlepas dari apakah `showForm` awalnya disetel ke `true` atau `false`. (Setel ke `false` dan perhatikan tidak ada pesan konsol tambahan.)
          diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json index af85858b5..72e26473c 100644 --- a/src/sidebarLearn.json +++ b/src/sidebarLearn.json @@ -190,7 +190,7 @@ "path": "/learn/synchronizing-with-effects" }, { - "title": "You Might Not Need an Effect", + "title": "And mungkin tidak membutuhkan Effect", "path": "/learn/you-might-not-need-an-effect" }, {