From a30e1f954dce2a67985caa8b2376a1839dbfa6cf Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Mon, 10 Jul 2023 03:26:39 -0500 Subject: [PATCH 001/675] update link to fault tolerance blog post (#6142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this is the canonical link now 🫡 --- src/content/reference/react/Component.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/reference/react/Component.md b/src/content/reference/react/Component.md index 006ade5d8d..569bf19f25 100644 --- a/src/content/reference/react/Component.md +++ b/src/content/reference/react/Component.md @@ -1393,7 +1393,7 @@ Then you can wrap a part of your component tree with it: If `Profile` or its child component throws an error, `ErrorBoundary` will "catch" that error, display a fallback UI with the error message you've provided, and send a production error report to your error reporting service. -You don't need to wrap every component into a separate error boundary. When you think about the [granularity of error boundaries,](https://aweary.dev/fault-tolerance-react/) consider where it makes sense to display an error message. For example, in a messaging app, it makes sense to place an error boundary around the list of conversations. It also makes sense to place one around every individual message. However, it wouldn't make sense to place a boundary around every avatar. +You don't need to wrap every component into a separate error boundary. When you think about the [granularity of error boundaries,](https://www.brandondail.com/posts/fault-tolerance-react) consider where it makes sense to display an error message. For example, in a messaging app, it makes sense to place an error boundary around the list of conversations. It also makes sense to place one around every individual message. However, it wouldn't make sense to place a boundary around every avatar. From dcb7bad37d486ece98e79e14f6cb64e3920c7f86 Mon Sep 17 00:00:00 2001 From: Muhammad Zakir Ramadhan <61570975+zakirkun@users.noreply.github.com> Date: Thu, 13 Jul 2023 11:47:49 +0700 Subject: [PATCH 002/675] docs: translate API Reference -> `react-dom` -> `hydrate` (#528) Co-authored-by: Muh Zakir Ramadhan Co-authored-by: M Haidar Hanif Co-authored-by: Resi Respati --- src/content/reference/react-dom/hydrate.md | 80 +++++++++++----------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/content/reference/react-dom/hydrate.md b/src/content/reference/react-dom/hydrate.md index 639c7ab25b..cf8f14d54e 100644 --- a/src/content/reference/react-dom/hydrate.md +++ b/src/content/reference/react-dom/hydrate.md @@ -4,15 +4,15 @@ title: hydrate -This API will be removed in a future major version of React. +API ini akan dihapus pada React versi mayor berikutnya. -In React 18, `hydrate` was replaced by [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Using `hydrate` in React 18 will warn that your app will behave as if it’s running React 17. Learn more [here.](/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis) +Di React 18, `hydrate` digantikan oleh [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) Menggunakan `hydrate` di React 18 akan memberi peringatan bahwa aplikasi Anda akan berperilaku seakan-akan sedang berjalan di React 17. Pelajari selengkapnya [di sini.](/blog/2022/03/08/react-18-upgrade-guide#updates-to-client-rendering-apis) -`hydrate` lets you display React components inside a browser DOM node whose HTML content was previously generated by [`react-dom/server`](/reference/react-dom/server) in React 17 and below. +`hydrate` memungkinkan Anda menampilkan komponen React di dalam simpul DOM peramban yang konten HTML-nya sebelumnya telah dihasilkan oleh [`react-dom/server`](/reference/react-dom/server) di React versi 17 ke bawah. ```js hydrate(reactNode, domNode, callback?) @@ -24,11 +24,11 @@ hydrate(reactNode, domNode, callback?) --- -## Reference {/*reference*/} +## Referensi {/*reference*/} ### `hydrate(reactNode, domNode, callback?)` {/*hydrate*/} -Call `hydrate` in React 17 and below to “attach” React to existing HTML that was already rendered by React in a server environment. +Memanggil `hydrate` di React 17 dan yang lebih rendah untuk "melekatkan" React ke HTML yang sudah ada sebelumnya yang dihasilkan oleh React di lingkungan *server*. ```js import { hydrate } from 'react-dom'; @@ -36,33 +36,33 @@ import { hydrate } from 'react-dom'; hydrate(reactNode, domNode); ``` -React will attach to the HTML that exists inside the `domNode`, and take over managing the DOM inside it. An app fully built with React will usually only have one `hydrate` call with its root component. +React akan melekat pada HTML yang ada di dalam `domNode`, dan mengambil alih pengelolaan DOM di dalamnya. Sebuah aplikasi yang sepenuhnya dibangun dengan React biasanya hanya akan memiliki satu pemanggilan `hydrate` dengan komponen akarnya. -[See more examples below.](#usage) +[Lihat lebih banyak contoh di bawah ini.](#usage) -#### Parameters {/*parameters*/} +#### Parameter {/*parameters*/} -* `reactNode`: The "React node" used to render the existing HTML. This will usually be a piece of JSX like `` which was rendered with a `ReactDOM Server` method such as `renderToString()` in React 17. +* `reactNode`: "Simpul React" yang digunakan untuk me-*render* HTML yang sudah ada. Ini biasanya berupa potongan JSX seperti `` yang di-*render* dengan metode (*method*) `ReactDOM Server` seperti `renderToString()` di React 17. -* `domNode`: A [DOM element](https://developer.mozilla.org/en-US/docs/Web/API/Element) that was rendered as the root element on the server. +* `domNode`: Sebuah [elemen DOM](https://developer.mozilla.org/en-US/docs/Web/API/Element) yang di-*render* sebagai elemen akar di *server*. -* **optional**: `callback`: A function. If passed, React will call it after your component is hydrated. +* **opsional**: `callback`: Sebuah fungsi. Jika diberikan, React akan memanggilnya setelah komponen Anda terhidrasi (*hydrated*). -#### Returns {/*returns*/} +#### Kembalian {/*returns*/} -`hydrate` returns null. +`hydrate` mengembalikan nilai kosong (*null*). -#### Caveats {/*caveats*/} -* `hydrate` expects the rendered content to be identical with the server-rendered content. React can patch up differences in text content, but you should treat mismatches as bugs and fix them. -* In development mode, React warns about mismatches during hydration. There are no guarantees that attribute differences will be patched up in case of mismatches. This is important for performance reasons because in most apps, mismatches are rare, and so validating all markup would be prohibitively expensive. -* You'll likely have only one `hydrate` call in your app. If you use a framework, it might do this call for you. -* If your app is client-rendered with no HTML rendered already, using `hydrate()` is not supported. Use [render()](/reference/react-dom/render) (for React 17 and below) or [createRoot()](/reference/react-dom/client/createRoot) (for React 18+) instead. +#### Catatan {/*caveats*/} +* `hydrate` mengharapkan konten yang di-*render* identik dengan konten yang di-*render* di *server*. React dapat memperbaiki perbedaan dalam konten teks, tetapi Anda seharusnya memperlakukan ketidakcocokan tersebut sebagai *bug* dan memperbaikinya sendiri. +* Dalam mode pengembangan, React memberi peringatan tentang ketidakcocokan selama hidrasi (*hydration*). Tidak ada jaminan bahwa perbedaan atribut akan diperbaiki dalam kasus ketidakcocokan. Ini penting untuk alasan kinerja karena dalam sebagian besar aplikasi, ketidakcocokan jarang terjadi, sehingga memvalidasi semua markup akan menjadi sangat mahal. +* Kemungkinan Anda hanya akan memiliki satu pemanggilan `hydrate` dalam aplikasi Anda. Jika Anda menggunakan sebuah *framework*, mungkin *framework* tersebut melakukan panggilan ini untuk Anda. +* Jika aplikasi Anda di-*render* oleh klien tanpa HTML yang sudah di-*render* sebelumnya, penggunaan `hydrate()` tidak didukung. Alih-alih menggunakan `hydrate()`, gunakanlah [render()](/reference/react-dom/render) (untuk React 17 dan lebih rendah) atau [createRoot()](/reference/react-dom/client/createRoot) (untuk React 18+). --- -## Usage {/*usage*/} +## Penggunaan {/*usage*/} -Call `hydrate` to attach a React component into a server-rendered browser DOM node. +Panggil `hydrate` untuk melekatkan sebuah komponen React kepada simpul DOM peramban yang di-*render* di *server*. ```js [[1, 3, ""], [2, 3, "document.getElementById('root')"]] import { hydrate } from 'react-dom'; @@ -70,22 +70,22 @@ import { hydrate } from 'react-dom'; hydrate(, document.getElementById('root')); ``` -Using `hydrate()` to render a client-only app (an app without server-rendered HTML) is not supported. Use [`render()`](/reference/react-dom/render) (in React 17 and below) or [`createRoot()`](/reference/react-dom/client/createRoot) (in React 18+) instead. +Penggunaan `hydrate()` untuk me-*render* aplikasi hanya di sisi klien (aplikasi tanpa HTML yang di-*render* oleh server) tidak didukung. Alih-alih menggunakan `hydrate()`, gunakanlah [`render()`](/reference/react-dom/render) (di React 17 dan lebih rendah) atau [`createRoot()`](/reference/react-dom/client/createRoot) (di React 18+). -### Hydrating server-rendered HTML {/*hydrating-server-rendered-html*/} +### Menghidrasi HTML yang di-*render* oleh server {/*hydrating-server-rendered-html*/} -In React, "hydration" is how React "attaches" to existing HTML that was already rendered by React in a server environment. During hydration, React will attempt to attach event listeners to the existing markup and take over rendering the app on the client. +Di React, "hidrasi" adalah bagaimana React "melekatkan" ke HTML yang sudah ada sebelumnya yang telah di-*render* oleh React di lingkungan *server*. Selama hidrasi, React akan mencoba untuk melekatkan *event listeners* ke *markup* yang ada dan mengambil alih pe-*render*-an aplikasi pada sisi klien. -In apps fully built with React, **you will usually only hydrate one "root", once at startup for your entire app**. +Pada aplikasi yang sepenuhnya dibangun dengan React, **biasanya Anda hanya akan menghidrasi satu "akar" ("*root*"), yakni sekali saat memulai menjalankan seluruh aplikasi Anda"**. ```html public/index.html -

Hello, world!

+

Halo, dunia!

``` ```js index.js active @@ -98,23 +98,23 @@ hydrate(, document.getElementById('root')); ```js App.js export default function App() { - return

Hello, world!

; + return

Halo, dunia!

; } ```
-Usually you shouldn't need to call `hydrate` again or to call it in more places. From this point on, React will be managing the DOM of your application. To update the UI, your components will [use state.](/reference/react/useState) +Biasanya Anda tidak perlu memanggil `hydrate` lagi atau memanggilnya di tempat lain. Dari titik ini, React akan mengelola DOM dari aplikasi Anda. Untuk memperbarui UI, komponen akan [menggunakan state.](/reference/react/useState) -For more information on hydration, see the docs for [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) +Untuk informasi lebih lanjut tentang hidrasi, lihat dokumen untuk [`hydrateRoot`.](/reference/react-dom/client/hydrateRoot) --- -### Suppressing unavoidable hydration mismatch errors {/*suppressing-unavoidable-hydration-mismatch-errors*/} +### Menghilangkan kesalahan ketidakcocokan hidrasi yang tidak dapat dihindari {/*suppressing-unavoidable-hydration-mismatch-errors*/} -If a single element’s attribute or text content is unavoidably different between the server and the client (for example, a timestamp), you may silence the hydration mismatch warning. +Jika satu atribut elemen atau konten teks secara tidak terhindarkan berbeda antara server dan klien (misalnya, timestamp), Anda dapat menghilangkan peringatan ketidakcocokan hidrasi. -To silence hydration warnings on an element, add `suppressHydrationWarning={true}`: +Untuk menghilangkan peringatan hidrasi pada elemen, tambahkan `suppressHydrationWarning={true}`: @@ -123,7 +123,7 @@ To silence hydration warnings on an element, add `suppressHydrationWarning={true HTML content inside
...
was generated from App by react-dom/server. --> -

Current Date: 01/01/2020

+

Tanggal Saat ini: 01/01/2020

``` ```js index.js @@ -138,7 +138,7 @@ hydrate(, document.getElementById('root')); export default function App() { return (

- Current Date: {new Date().toLocaleDateString()} + Tanggal Saat ini: {new Date().toLocaleDateString()}

); } @@ -146,13 +146,13 @@ export default function App() {
-This only works one level deep, and is intended to be an escape hatch. Don’t overuse it. Unless it’s text content, React still won’t attempt to patch it up, so it may remain inconsistent until future updates. +Cara ini hanya berfungsi untuk satu tingkat kedalaman dan dimaksudkan untuk menjadi jalan keluar ketika tidak ada pilihan lain. Jangan terlalu sering menggunakannya. Kecuali jika itu adalah konten teks, React masih tidak akan mencoba memperbaikinya, sehingga konten tersebut mungkin tetap tidak konsisten sampai dengan pembaruan di versi-versi mendatang. --- -### Handling different client and server content {/*handling-different-client-and-server-content*/} +### Mengatasi konten berbeda antara client dan server {/*handling-different-client-and-server-content*/} -If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a [state variable](/reference/react/useState) like `isClient`, which you can set to `true` in an [effect](/reference/react/useEffect): +Jika Anda sengaja perlu me-*render* sesuatu yang berbeda di server dan klien, Anda dapat melakukan dua kali pe-*render*-an. Komponen yang me-*render* sesuatu yang berbeda pada sisi klien dapat di baca di [*variabel state*](/reference/react/useState) seperti `isClient`, yang dapat ditetapkan ke `true` dalam sebuah [efek](/reference/react/useEffect): @@ -192,10 +192,10 @@ export default function App() { -This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. +Dengan cara ini, proses *render* awal akan me-*render* konten yang sama dengan *server*, menghindari ketidakcocokan, tetapi ada tambahan proses synchronously setelah hidrasi. -This approach makes hydration slower because your components have to render twice. Be mindful of the user experience on slow connections. The JavaScript code may load significantly later than the initial HTML render, so rendering a different UI immediately after hydration may feel jarring to the user. +Metode ini membuat hidrasi lebih lambat karena komponen Anda harus di-*render* dua kali. Pertimbangkan pengalaman pengguna pada koneksi yang lambat. Kode JavaScript dapat dimuat jauh setelah *render* HTML awal, sehingga me-*render* UI yang berbeda langsung setelah hidrasi dapat terasa tidak nyaman bagi pengguna. From 3ea3506aa90d0ca1ca7de7ff9c5467174889be61 Mon Sep 17 00:00:00 2001 From: Thaddeus Cleo <37366858+thaddeuscleo@users.noreply.github.com> Date: Mon, 17 Jul 2023 11:50:57 +0700 Subject: [PATCH 003/675] docs: translate Extracting State Logic into a Reducer (#544) Co-authored-by: Irfan Maulana Co-authored-by: Resi Respati Co-authored-by: M Haidar Hanif --- .../extracting-state-logic-into-a-reducer.md | 253 +++++++++--------- 1 file changed, 127 insertions(+), 126 deletions(-) diff --git a/src/content/learn/extracting-state-logic-into-a-reducer.md b/src/content/learn/extracting-state-logic-into-a-reducer.md index 012a5c3f9d..4e1efb91eb 100644 --- a/src/content/learn/extracting-state-logic-into-a-reducer.md +++ b/src/content/learn/extracting-state-logic-into-a-reducer.md @@ -1,25 +1,25 @@ --- -title: Extracting State Logic into a Reducer +title: Mengekstraksi Logika State ke Reducer --- -Components with many state updates spread across many event handlers can get overwhelming. For these cases, you can consolidate all the state update logic outside your component in a single function, called a _reducer._ +Komponen dengan banyak pembaruan *state* yang tersebar di banyak *event handlers* bisa menjadi sangat membingungkan. Untuk kasus seperti ini, Anda dapat menggabungkan semua logika pembaruan *state* di luar komponen Anda dalam satu fungsi, yang disebut sebagai *reducer*. -- What a reducer function is -- How to refactor `useState` to `useReducer` -- When to use a reducer -- How to write one well +- Apa itu fungsi *reducer* +- Bagaimana cara untuk migrasi dari fungsi `useState` menjadi `useReducer` +- Kapan menggunakan fungsi *reducer* +- Bagaimana cara menulis fungsi *reducer* dengan baik -## Consolidate state logic with a reducer {/*consolidate-state-logic-with-a-reducer*/} +## Mengkonsolidasikan logika state menggunakan reducer {/*consolidate-state-logic-with-a-reducer*/} -As your components grow in complexity, it can get harder to see at a glance all the different ways in which a component's state gets updated. For example, the `TaskApp` component below holds an array of `tasks` in state and uses three different event handlers to add, remove, and edit tasks: +Saat komponen-komponen Anda semakin kompleks, hal ini mengakibatkan berbagai cara memperbarui *state* komponen dalam kode menjadi sulit untuk dilihat secara sekilas. Contoh, komponen `TaskApp` di bawah menyimpan senarai `tasks` dalam *state* dan menggunakan tiga *handler* untuk menambahkan, menghapus dan mengubah `tasks`: @@ -73,9 +73,9 @@ export default function TaskApp() { let nextId = 3; const initialTasks = [ - {id: 0, text: 'Visit Kafka Museum', done: true}, - {id: 1, text: 'Watch a puppet show', done: false}, - {id: 2, text: 'Lennon Wall pic', done: false}, + {id: 0, text: 'Mengunjungi Museum Kafka', done: true}, + {id: 1, text: 'Tonton pertunjukan boneka', done: false}, + {id: 2, text: 'Foto Lennon Wall', done: false}, ]; ``` @@ -179,17 +179,17 @@ li { -Each of its event handlers calls `setTasks` in order to update the state. As this component grows, so does the amount of state logic sprinkled throughout it. To reduce this complexity and keep all your logic in one easy-to-access place, you can move that state logic into a single function outside your component, **called a "reducer".** +Setiap *event handler* pada komponen `TaskApp` akan memangil fungsi `setTask` untuk melakukan pembaharuan *state*. Saat komponen ini bertumbuh semakin besar, logika *state* akan semakin rumit. Untuk mengurangi kompleksitas dan menyimpan semua logika Anda di satu tempat yang mudah diakses. Anda dapat memindahkan logika *state* tersebut ke sebuah fungsi di luar komponen Anda, yang **disebut *"reducer"*.** -Reducers are a different way to handle state. You can migrate from `useState` to `useReducer` in three steps: +_Reducer_ merupakan sebuah alternatif untuk menangani *state*. Anda dapat migrasi dari `useState` ke `useReducer` dalam tiga langkah: -1. **Move** from setting state to dispatching actions. -2. **Write** a reducer function. -3. **Use** the reducer from your component. +1. **Pindah** dari menyetel *state* menjadi men-*dispatch* *action* +2. **Tulis** fungsi *reducer*. +3. **Gunakan** *reducer* pada komponen Anda. -### Step 1: Move from setting state to dispatching actions {/*step-1-move-from-setting-state-to-dispatching-actions*/} +### Langkah 1: Pindah dari menyetel state menjadi men-dispatch action {/*step-1-move-from-setting-state-to-dispatching-actions*/} -Your event handlers currently specify _what to do_ by setting state: +*Event handler* Anda sekarang menentukan apa *yang harus dilakukan* dengan menyetel *state*: ```js function handleAddTask(text) { @@ -220,13 +220,13 @@ function handleDeleteTask(taskId) { } ``` -Remove all the state setting logic. What you are left with are three event handlers: +Hapus semua logika untuk mengatur *state*. Maka yang tersisa adalah tiga *event handlers*: -- `handleAddTask(text)` is called when the user presses "Add". -- `handleChangeTask(task)` is called when the user toggles a task or presses "Save". -- `handleDeleteTask(taskId)` is called when the user presses "Delete". +- `handleAddTask(text)` akan dipanggil ketika pengguna menekan tombol "Add". +- `handleChangeTask(task)` akan dipanggil ketika pengguna matikan sebuah task atau menekan tombol "Save". +- `handleDeleteTask(taskId)` akan dipanggil ketika pengguna menekan tombol "Delete". -Managing state with reducers is slightly different from directly setting state. Instead of telling React "what to do" by setting state, you specify "what the user just did" by dispatching "actions" from your event handlers. (The state update logic will live elsewhere!) So instead of "setting `tasks`" via an event handler, you're dispatching an "added/changed/deleted a task" action. This is more descriptive of the user's intent. +Mengelola *state* dengan fungsi *reducer* akan sedikit berbeda dari memanggil fungsi penetap *state* secara langsung. Alih-alih memberi tahu React "apa yang harus dilakukan" dengan menetapkan *state*, Anda merincis "apa yang baru saja dilakukan pengguna" dengan mengirim "aksi" dari *event handler* Anda. (Logika pembaruan *state* akan berada di tempat lain!) Jadi, alih-alih "mengatur `tasks`" melalui *event handler*, Anda mengirim aksi "menambahkan/mengubah/menghapus task". Cara ini lebih deskriptif untuk mengetahui intensi aksi pengguna. ```js function handleAddTask(text) { @@ -252,12 +252,12 @@ function handleDeleteTask(taskId) { } ``` -The object you pass to `dispatch` is called an "action": +Objek yang anda masukan ke `dispatch` dapat disebut sebagai "aksi": ```js {3-7} function handleDeleteTask(taskId) { dispatch( - // "action" object: + // objek "aksi": { type: 'deleted', id: taskId, @@ -266,43 +266,43 @@ function handleDeleteTask(taskId) { } ``` -It is a regular JavaScript object. You decide what to put in it, but generally it should contain the minimal information about _what happened_. (You will add the `dispatch` function itself in a later step.) +Contoh diatas merupakan objek JavaScript biasa. Anda dapat menentukan objek apapun yang akan dimasukkan ke dalamnya, tetapi umumnya harus berisi sebuah objek yang setidaknya berisi informasi *Apa yang terjadi*. (Anda akan menambahkan fungsi `dispatch` itu sendiri di langkah selanjutnya.) -An action object can have any shape. +Objek aksi bisa memiliki berbagai macam bentuk. -By convention, it is common to give it a string `type` that describes what happened, and pass any additional information in other fields. The `type` is specific to a component, so in this example either `'added'` or `'added_task'` would be fine. Choose a name that says what happened! +Secara konvensi, umumnya objek aksi diberikan sebuah properti `type` bertipe *string* yang menjelaskan aksi apa yang telah terjadi. Dan menambahkan informasi tambahan pada properti lainnya. Properti `type` ini khusus untuk komponen, maka untuk contoh ini nilai `'added'` atau `'added_task'` akan baik-baik saja diberikan pada properti `type`. Tentukan nama yang dapat menjelaskan aksi apa yang terjadi! ```js dispatch({ - // specific to component + // khusus untuk komponen type: 'what_happened', - // other fields go here + // properti lain disini }); ``` -### Step 2: Write a reducer function {/*step-2-write-a-reducer-function*/} +### Langkah 2: Tulis fungsi reducer {/*step-2-write-a-reducer-function*/} -A reducer function is where you will put your state logic. It takes two arguments, the current state and the action object, and it returns the next state: +Fungsi *reducer* merupakan tempat dimana Anda meletakkan logika *state*. Fungsi *reducer* menerima dua argumen yaitu *state* sekarang dan object aksi, dan mengembalikan *state* berikutnya: ```js function yourReducer(state, action) { - // return next state for React to set + // mengembalikan state berikutnya untuk ditetapkan oleh React } ``` -React will set the state to what you return from the reducer. +React akan menetapkan *state* yang dikembalikan oleh *reducer* Anda. -To move your state setting logic from your event handlers to a reducer function in this example, you will: +Untuk memindahkan logika fungsi penetap *state* dari *event handler* Anda menjadi fungsi reducer pada contoh berikut, Anda akan: -1. Declare the current state (`tasks`) as the first argument. -2. Declare the `action` object as the second argument. -3. Return the _next_ state from the reducer (which React will set the state to). +1. Deklarasikan *state*(`tasks`) saat ini sebagai argumen pertama. +2. Deklarasikan objek `action` sebagai argumen kedua. +3. Mengembalikan *state* berikutnya melalui reducer (di mana React akan mengatur statusnya). -Here is all the state setting logic migrated to a reducer function: +Berikut merupakan logika funsi penetap *state* yang dimigrasikan ke fungsi *reducer*: ```js function tasksReducer(tasks, action) { @@ -330,14 +330,14 @@ function tasksReducer(tasks, action) { } } ``` - -Because the reducer function takes state (`tasks`) as an argument, you can **declare it outside of your component.** This decreases the indentation level and can make your code easier to read. +Karena fungsi *reducer* menerima *state* (`tasks`) sebagai sebuah argument, **Anda dapat mendeklarasikan fungsi *reducer* di luar komponen Anda.** Dengan ini kode yang Anda tulis tingkat indentasinya akan berkurang dan akan lebih mudah dibaca. -The code above uses if/else statements, but it's a convention to use [switch statements](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/switch) inside reducers. The result is the same, but it can be easier to read switch statements at a glance. +Kode di atas menggunakan pernyataan *if/else*, namun secara konvensi ketika menggunakan fungsi reducer Anda harus menggunakan [pernyataan *switch*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/switch) pada fungsi tersebut. Secara hasil akan sama, tetapi akan lebih mudah dibaca secara sekilas. -We'll be using them throughout the rest of this documentation like so: + +Kami akan menggunakan pernyataan *switch* sepanjang sisa dokumentasi ini sebagai berikut: ```js function tasksReducer(tasks, action) { @@ -371,19 +371,19 @@ function tasksReducer(tasks, action) { } ``` -We recommend wrapping each `case` block into the `{` and `}` curly braces so that variables declared inside of different `case`s don't clash with each other. Also, a `case` should usually end with a `return`. If you forget to `return`, the code will "fall through" to the next `case`, which can lead to mistakes! +Kami merekomendasikan sebaiknya menggabungkan masing-masing blok `case` ke dalam kurung kurawal `{` dan `}` sehingga variabel yang dideklarasikan di dalam `case` berbeda tidak bentrok satu sama lain. Dan juga, sebuah `case` biasanya diakhiri dengan `return`. Jika Anda lupa mengakhiri dengan `return`, kode akan lanjut ke `case` berikutnya, yang akan menyebabkan keselahan. -If you're not yet comfortable with switch statements, using if/else is completely fine. +Jika Anda belum nyaman menggunakan pernyataan *switch*, menggunakan pernyataan *if/else* itu tidak apa-apa. -#### Why are reducers called this way? {/*why-are-reducers-called-this-way*/} +#### Mengapa fungsi reducer disebut seperti ini? {/*why-are-reducers-called-this-way*/} -Although reducers can "reduce" the amount of code inside your component, they are actually named after the [`reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) operation that you can perform on arrays. +Meskipun *reducer* dapat "mengurangi" jumlah kode di dalam komponen Anda, sebenarnya mereka dinamakan setelah operasi [`reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) yang dapat dilakukan pada senarai. -The `reduce()` operation lets you take an array and "accumulate" a single value out of many: +Operasi `reduce()` memungkinkan Anda untuk mengambil sebuah senarai dan "mengakumulasi" sebuah nilai tunggal dari banyak nilai: ``` const arr = [1, 2, 3, 4, 5]; @@ -392,9 +392,9 @@ const sum = arr.reduce( ); // 1 + 2 + 3 + 4 + 5 ``` -The function you pass to `reduce` is known as a "reducer". It takes the _result so far_ and the _current item,_ then it returns the _next result._ React reducers are an example of the same idea: they take the _state so far_ and the _action_, and return the _next state._ In this way, they accumulate actions over time into state. +Fungsi yang Anda berikan ke `reduce` dikenal sebagai "reducer". Fungsi tersebut mengambil *hasil sejauh ini* dan *item saat ini*, kemudian mengembalikan *hasil berikutnya*. *Reducer* di React adalah contoh dari ide yang sama: mereka mengambil *state sejauh ini* dan *aksi*, lalu mengembalikan state berikutnya. Dengan cara ini, *reducer* mengakumulasi aksi dari waktu ke waktu ke dalam *state*. -You could even use the `reduce()` method with an `initialState` and an array of `actions` to calculate the final state by passing your reducer function to it: +Anda bahkan dapat menggunakan metode `reduce()` dengan `initialState` dan sebuah senarai `aksi` untuk menghitung *state* akhir dengan melewatkan fungsi *reducer* ke dalamnya: @@ -453,43 +453,43 @@ export default function tasksReducer(tasks, action) { -You probably won't need to do this yourself, but this is similar to what React does! +Anda mungkin tidak perlu melakukannya sendiri, tetapi ini mirip dengan apa yang dilakukan oleh React! -### Step 3: Use the reducer from your component {/*step-3-use-the-reducer-from-your-component*/} +### Langkah 3: Gunakan reducer pada komponen Anda {/*step-3-use-the-reducer-from-your-component*/} -Finally, you need to hook up the `tasksReducer` to your component. Import the `useReducer` Hook from React: +Terakhir, Anda perlu menghubungkan fungsi `tasksReducer` ke komponen Anda. Impor fungsi *hook* `useReducer` dari React: ```js import { useReducer } from 'react'; ``` -Then you can replace `useState`: +Kemudian Anda dapat mengganti fungsi `useState`: ```js const [tasks, setTasks] = useState(initialTasks); ``` -with `useReducer` like so: +dengan fungsi `useReducer` seperti ini: ```js const [tasks, dispatch] = useReducer(tasksReducer, initialTasks); ``` -The `useReducer` Hook is similar to `useState`—you must pass it an initial state and it returns a stateful value and a way to set state (in this case, the dispatch function). But it's a little different. +Fungsi hook `useReducer` mirip dengan fungsi `useState`—Anda harus melewatkan *state* awal ke dalamnya dan ia mengembalikan nilai _stateful_ dan cara untuk mengatur *state* (dalam kasus ini, fungsi *dispatch*). Namun, sedikit berbeda. -The `useReducer` Hook takes two arguments: +Fungsi *hook* `useReducer` mengambil dua argumen: -1. A reducer function -2. An initial state +1. Fungsi *reducer*. +2. *State* awal. -And it returns: +Dan fungsi *hook* `useReducer` mengembalikan: -1. A stateful value -2. A dispatch function (to "dispatch" user actions to the reducer) +1. Nilai *statefull*. +2. Fungsi *dispatch* (untuk "men-dispatch" aksi pengguna ke fungsi *reducer*). -Now it's fully wired up! Here, the reducer is declared at the bottom of the component file: +Sekarang fungsi *reducer* sudah sepenuhnya terhubung! Di sini, fungsi *reducer* dinyatakan di bagian bawah file komponen: @@ -674,7 +674,7 @@ li { -If you want, you can even move the reducer to a different file: +Hal ini opsional, namun jika Anda ingin melakukannya, Anda bahkan dapat memindahkan reducer ke file yang berbeda: @@ -862,30 +862,30 @@ li { -Component logic can be easier to read when you separate concerns like this. Now the event handlers only specify _what happened_ by dispatching actions, and the reducer function determines _how the state updates_ in response to them. +Logika komponen akan dapat lebih mudah dibaca ketika Anda memisahkan aspek seperti ini. Sekarang, *event handler* hanya menentukan *apa yang terjadi* dengan mengirimkan aksi, dan fungsi reducer menentukan *bagaimana state diperbarui* sebagai respons terhadapnya. -## Comparing `useState` and `useReducer` {/*comparing-usestate-and-usereducer*/} +## Membandingkan `useState` dan `useReducer` {/*comparing-usestate-and-usereducer*/} -Reducers are not without downsides! Here's a few ways you can compare them: +*Reducers* tidaklah tanpa kekurangan! Berikut adalah beberapa cara untuk membandingkannya: -- **Code size:** Generally, with `useState` you have to write less code upfront. With `useReducer`, you have to write both a reducer function _and_ dispatch actions. However, `useReducer` can help cut down on the code if many event handlers modify state in a similar way. -- **Readability:** `useState` is very easy to read when the state updates are simple. When they get more complex, they can bloat your component's code and make it difficult to scan. In this case, `useReducer` lets you cleanly separate the _how_ of update logic from the _what happened_ of event handlers. -- **Debugging:** When you have a bug with `useState`, it can be difficult to tell _where_ the state was set incorrectly, and _why_. With `useReducer`, you can add a console log into your reducer to see every state update, and _why_ it happened (due to which `action`). If each `action` is correct, you'll know that the mistake is in the reducer logic itself. However, you have to step through more code than with `useState`. -- **Testing:** A reducer is a pure function that doesn't depend on your component. This means that you can export and test it separately in isolation. While generally it's best to test components in a more realistic environment, for complex state update logic it can be useful to assert that your reducer returns a particular state for a particular initial state and action. -- **Personal preference:** Some people like reducers, others don't. That's okay. It's a matter of preference. You can always convert between `useState` and `useReducer` back and forth: they are equivalent! +- **Ukuran kode:** Secara umum, dengan `useState` kamu harus menulis lebih sedikit kode di awal. Dengan `useReducer`, kamu harus menulis baik fungsi *reducer* *dan* dispatch actions. Namun, `useReducer` dapat membantu mengurangi jumlah kode jika banyak *event handler* memodifikasi *state* dengan cara yang serupa. +- **Keterbacaan:** `useState` sangat mudah dibaca ketika pembaruan *state* sederhana. Ketika pembaruan semakin kompleks, mereka dapat membesarkan kode komponen dan sulit untuk dipindai. Dalam hal ini, `useReducer` memungkinkan kamu untuk memisahkan dengan jelas *bagaimana* logika pembaruan dipisahkan dari *apa yang terjadi* pada *event handler*. +- **Debugging:** Ketika kamu memiliki *bug* dengan `useState`, sulit untuk mengetahui di mana *state* diatur dengan tidak benar, dan mengapa. Dengan `useReducer`, kamu dapat menambahkan log konsol ke *reducer* kamu untuk melihat setiap pembaruan *state*, dan *mengapa* itu terjadi (karena `aksi` apa). Jika setiap `aksi` benar, kamu akan tahu bahwa kesalahan ada di logika *reducer* itu sendiri. Namun, kamu harus melalui kode yang lebih banyak daripada `useState`. +- **Testing:** Sebuah *reducer* adalah fungsi murni yang tidak bergantung pada komponen kamu. Ini berarti kamu dapat mengekspor dan mengujinya secara terpisah secara isolasi. Meskipun secara umum lebih baik untuk menguji komponen dalam lingkungan yang lebih realistis, untuk logika pembaruan *state* yang kompleks dapat berguna untuk menegaskan bahwa *reducer* kamu mengembalikan *state* tertentu untuk *state* awal dan aksi tertentu. +- **Preferensi pribadi:** Beberapa orang suka *reducers*, yang lain tidak. Itu tidak apa-apa. Ini hanya masalah preferensi. Kamu selalu dapat mengonversi antara fungsi `useState` dan `useReducer` bolak-balik: mereka setara! -We recommend using a reducer if you often encounter bugs due to incorrect state updates in some component, and want to introduce more structure to its code. You don't have to use reducers for everything: feel free to mix and match! You can even `useState` and `useReducer` in the same component. +Kami merekomendasikan menggunakan reducer jika kamu sering menghadapi *bug* karena pembaruan state yang salah dibeberapa komponen, dan ingin memperkenalkan lebih banyak struktur pada kode-nya. Kamu tidak harus menggunakan fungsi *reducers* untuk semuanya: bebas untuk mencampur dan mencocokkan! Kamu bahkan dapat menggunakan `useState` dan `useReducer` di komponen yang sama. -## Writing reducers well {/*writing-reducers-well*/} +## Menulis fungsi reduksi dengan baik {/*writing-reducers-well*/} -Keep these two tips in mind when writing reducers: +Ingatlah dua tips ini saat menulis fungsi reducers: -- **Reducers must be pure.** Similar to [state updater functions](/learn/queueing-a-series-of-state-updates), reducers run during rendering! (Actions are queued until the next render.) This means that reducers [must be pure](/learn/keeping-components-pure)—same inputs always result in the same output. They should not send requests, schedule timeouts, or perform any side effects (operations that impact things outside the component). They should update [objects](/learn/updating-objects-in-state) and [arrays](/learn/updating-arrays-in-state) without mutations. -- **Each action describes a single user interaction, even if that leads to multiple changes in the data.** For example, if a user presses "Reset" on a form with five fields managed by a reducer, it makes more sense to dispatch one `reset_form` action rather than five separate `set_field` actions. If you log every action in a reducer, that log should be clear enough for you to reconstruct what interactions or responses happened in what order. This helps with debugging! +- **Reducer harus murni (pure).** Sama seperti [fungsi updater state](/learn/queueing-a-series-of-state-updates), *reducer* dijalankan selama proses *rendering*! (Aksi diantre sampai *render* selanjutnya.) Ini berarti bahwa *reducer* [harus murni](/learn/keeping-components-pure) - input yang sama selalu menghasilkan output yang sama. Mereka tidak boleh mengirim permintaan, menjadwalkan waktu tunggu, atau melakukan efek samping (operasi yang memengaruhi hal-hal di luar komponen). Mereka harus memperbarui [objek](/learn/updating-objects-in-state) dan [senarai](/learn/updating-arrays-in-state) tanpa mutasi. +- **Setiap aksi menjelaskan satu interaksi pengguna, meskipun itu mengakibatkan beberapa perubahan pada data.** Sebagai contoh, jika pengguna menekan "Reset" pada formulir dengan lima field yang dikelola oleh reducer, lebih baik untuk mengirimkan satu aksi `reset_form` daripada lima aksi `set_field` terpisah. Jika Anda mencatat setiap aksi dalam fungsi *reducer*, log tersebut harus cukup jelas bagi Anda untuk merekonstruksi interaksi atau respon apa yang terjadi dalam urutan apa. Ini membantu dalam proses debugging! -## Writing concise reducers with Immer {/*writing-concise-reducers-with-immer*/} +## Menulis Reducer yang singkat dengan Immer {/*writing-concise-reducers-with-immer*/} -Just like with [updating objects](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) and [arrays](/learn/updating-arrays-in-state#write-concise-update-logic-with-immer) in regular state, you can use the Immer library to make reducers more concise. Here, [`useImmerReducer`](https://github.com/immerjs/use-immer#useimmerreducer) lets you mutate the state with `push` or `arr[i] =` assignment: +Sama seperti [memperbarui objek](/learn/updating-objects-in-state#write-concise-update-logic-with-immer) dan [senarai](/learn/updating-arrays-in-state#write-concise-update-logic-with-immer) pada *state* biasa, Anda dapat menggunakan pustaka *Immer* untuk membuat *reducer* lebih ringkas. Di sini, [`useImmerReducer`](https://github.com/immerjs/use-immer#useimmerreducer) memungkinkan Anda memutasi *state* dengan `push` atau `arr[i] =` assignment: @@ -1082,34 +1082,36 @@ li { -Reducers must be pure, so they shouldn't mutate state. But Immer provides you with a special `draft` object which is safe to mutate. Under the hood, Immer will create a copy of your state with the changes you made to the `draft`. This is why reducers managed by `useImmerReducer` can mutate their first argument and don't need to return state. +Fungsi *reducers* harus bersifat murni, sehingga tidak boleh mengubah state. Tetapi Immer memberikan objek khusus `draft` yang aman untuk dimutasi. Di bawah kap mesin, Immer akan membuat salinan dari *state* Anda dengan perubahan yang dibuat pada `draft`. Inilah sebabnya mengapa *reducer* yang dikelola oleh `useImmerReducer` dapat memutasi argumen pertama mereka dan tidak perlu mengembalikan *state*. -- To convert from `useState` to `useReducer`: - 1. Dispatch actions from event handlers. - 2. Write a reducer function that returns the next state for a given state and action. - 3. Replace `useState` with `useReducer`. -- Reducers require you to write a bit more code, but they help with debugging and testing. -- Reducers must be pure. -- Each action describes a single user interaction. -- Use Immer if you want to write reducers in a mutating style. +- Untuk mengkonversi dari `useState` ke `useReducer`: + 1. Kirim aksi dari *event handler*. + 2. Tulis fungsi *reducer* yang mengembalikan state selanjutnya untuk state dan aksi yang diberikan. + 3. Ganti `useState` dengan `useReducer`. +- Reducer membutuhkan Anda untuk menulis sedikit lebih banyak kode, tetapi membantu dengan debugging dan pengujian. +- Fungsi reducer harus murni. +- Setiap aksi menggambarkan satu interaksi pengguna. +- Gunakan Immer jika Anda ingin menulis fungsi *reducer* dalam gaya yang dapat dimutasi. -#### Dispatch actions from event handlers {/*dispatch-actions-from-event-handlers*/} +#### Meneruskan aksi dari event handlers {/*dispatch-actions-from-event-handlers*/} + +Meneruskan tindakan dari penangan acara -Currently, the event handlers in `ContactList.js` and `Chat.js` have `// TODO` comments. This is why typing into the input doesn't work, and clicking on the buttons doesn't change the selected recipient. +Saat ini, penangan acara di `ContactList.js` dan `Chat.js` memiliki komentar `// TODO`. Inilah mengapa mengetik ke input tidak berfungsi, dan mengklik tombol tidak mengubah penerima yang dipilih. -Replace these two `// TODO`s with the code to `dispatch` the corresponding actions. To see the expected shape and the type of the actions, check the reducer in `messengerReducer.js`. The reducer is already written so you won't need to change it. You only need to dispatch the actions in `ContactList.js` and `Chat.js`. +Gantikan dua `// TODO` ini dengan kode untuk `meneruskan` tindakan yang sesuai. Untuk melihat bentuk yang diharapkan dan jenis tindakan, periksa pengurang dalam `messengerReducer.js`. Pengurang sudah ditulis sehingga Anda tidak perlu mengubahnya. Anda hanya perlu meneruskan tindakan di `ContactList.js` dan `Chat.js`. -The `dispatch` function is already available in both of these components because it was passed as a prop. So you need to call `dispatch` with the corresponding action object. +Fungsi `dispatch` sudah tersedia di kedua komponen ini karena telah dilewatkan sebagai prop. Jadi, Anda perlu memanggil `dispatch` dengan objek tindakan yang sesuai. -To check the action object shape, you can look at the reducer and see which `action` fields it expects to see. For example, the `changed_selection` case in the reducer looks like this: +Untuk memeriksa bentuk objek tindakan, Anda dapat melihat *reducer* dan melihat bidang `action` apa yang diharapkan. Misalnya, kasus `changed_selection` dalam *reducer* terlihat seperti ini: ```js case 'changed_selection': { @@ -1119,8 +1121,7 @@ case 'changed_selection': { }; } ``` - -This means that your action object should have a `type: 'changed_selection'`. You also see the `action.contactId` being used, so you need to include a `contactId` property into your action. +Ini berarti bahwa objek aksi Anda harus memiliki `type: 'changed_selection'`. Anda juga melihat penggunaan `action.contactId`, sehingga Anda perlu menyertakan properti `contactId` ke dalam aksi Anda. @@ -1220,7 +1221,7 @@ export default function Chat({contact, message, dispatch}) { placeholder={'Chat to ' + contact.name} onChange={(e) => { // TODO: dispatch edited_message - // (Read the input value from e.target.value) + // (Baca nilai input dari e.target.value.) }} />
@@ -1256,7 +1257,7 @@ textarea { -From the reducer code, you can infer that actions need to look like this: +Dari kode *reducer*, kamu dapat menyimpulkan bahwa tampilan aksi seperti ini: ```js // When the user presses "Alice" @@ -1272,7 +1273,7 @@ dispatch({ }); ``` -Here is the example updated to dispatch the corresponding messages: +Berikut adalah contoh yang diperbarui untuk mengirimkan pesan yang sesuai: @@ -1411,12 +1412,12 @@ textarea { -#### Clear the input on sending a message {/*clear-the-input-on-sending-a-message*/} +#### Kosongkan input setelah mengirim pesan {/*clear-the-input-on-sending-a-message*/} -Currently, pressing "Send" doesn't do anything. Add an event handler to the "Send" button that will: +Saat ini, menekan tombol "Kirim" tidak melakukan apa-apa. Tambahkan *event handler* ke tombol "Kirim" yang akan: -1. Show an `alert` with the recipient's email and the message. -2. Clear the message input. +1. Menampilkan `alert` dengan email penerima dan pesan. +2. Membersihkan input pesan. @@ -1555,7 +1556,7 @@ textarea { -There are a couple of ways you could do it in the "Send" button event handler. One approach is to show an alert and then dispatch an `edited_message` action with an empty `message`: +Ada beberapa cara yang dapat Anda lakukan pada *event handler* tombol "Kirim". Salah satu pendekatan yang bisa digunakan adalah menampilkan sebuah alert dan kemudian melewatkan aksi `edited_message` dengan `message` kosong: @@ -1701,9 +1702,9 @@ textarea { -This works and clears the input when you hit "Send". +Ini bekerja dan menghapus input saat Anda mengklik "Kirim". -However, _from the user's perspective_, sending a message is a different action than editing the field. To reflect that, you could instead create a _new_ action called `sent_message`, and handle it separately in the reducer: +Namun, *dari perspektif pengguna*, mengirim pesan adalah tindakan yang berbeda dengan mengedit kolom input. Untuk mencerminkan hal tersebut, Anda dapat membuat tindakan *baru* yang disebut `sent_message`, dan menanganinya secara terpisah di *reducer*: @@ -1854,32 +1855,32 @@ textarea { -The resulting behavior is the same. But keep in mind that action types should ideally describe "what the user did" rather than "how you want the state to change". This makes it easier to later add more features. +Perilaku hasilnya sama. Namun, perlu diingat bahwa jenis tindakan sebaiknya menjelaskan "apa yang dilakukan pengguna" daripada "bagaimana Anda ingin mengubah status". Ini memudahkan untuk menambahkan fitur lebih lanjut di kemudian hari. -With either solution, it's important that you **don't** place the `alert` inside a reducer. The reducer should be a pure function--it should only calculate the next state. It should not "do" anything, including displaying messages to the user. That should happen in the event handler. (To help catch mistakes like this, React will call your reducers multiple times in Strict Mode. This is why, if you put an alert in a reducer, it fires twice.) +Dengan salah satu solusi tersebut, penting bahwa Anda **tidak** menempatkan `alert` di dalam *reducer*. *Reducer* harus menjadi fungsi murni - hanya menghitung status selanjutnya. Ini tidak boleh "melakukan" apa pun, termasuk menampilkan pesan ke pengguna. Itu harus terjadi di *event handler*. (Untuk membantu menangkap kesalahan seperti ini, React akan memanggil *reducer* Anda beberapa kali di *Strict Mode*. Ini sebabnya, jika Anda menempatkan *alert* dalam *reducer*, *alert* itu akan muncul dua kali.) -#### Restore input values when switching between tabs {/*restore-input-values-when-switching-between-tabs*/} +#### Memulihkan nilai input saat beralih antar tab {/*restore-input-values-when-switching-between-tabs*/} -In this example, switching between different recipients always clears the text input: +Pada contoh ini, beralih antara penerima yang berbeda selalu menghapus *input* teks: ```js case 'changed_selection': { return { ...state, selectedId: action.contactId, - message: '' // Clears the input + message: '' // Mengosongkan input }; ``` -This is because you don't want to share a single message draft between several recipients. But it would be better if your app "remembered" a draft for each contact separately, restoring them when you switch contacts. +Ini karena Anda tidak ingin membagikan draf pesan tunggal di antara beberapa penerima. Namun, lebih baik jika aplikasi Anda "mengingat" draf untuk setiap kontak secara terpisah, mengembalikannya saat Anda beralih kontak. -Your task is to change the way the state is structured so that you remember a separate message draft _per contact_. You would need to make a few changes to the reducer, the initial state, and the components. +Tugas Anda adalah mengubah cara struktur *state* sehingga Anda mengingatkan draf pesan terpisah per kontak. Anda perlu melakukan beberapa perubahan pada *reducer*, *state* awal, dan komponen. -You can structure your state like this: +Anda dapat strukturkan *state* Anda seperti ini: ```js export const initialState = { @@ -1891,7 +1892,7 @@ export const initialState = { }; ``` -The `[key]: value` [computed property](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names) syntax can help you update the `messages` object: +Syntax `[key]: value` [properti terhitung](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names) dapat membantu Anda memperbarui objek `messages`: ```js { @@ -2053,7 +2054,7 @@ textarea { -You'll need to update the reducer to store and update a separate message draft per contact: +Anda perlu memperbarui *reducer* untuk menyimpan dan memperbarui draft pesan terpisah untuk setiap kontak: ```js // When the input is edited @@ -2071,13 +2072,13 @@ case 'edited_message': { } ``` -You would also update the `Messenger` component to read the message for the currently selected contact: +Anda juga harus memperbarui komponen `Messenger` untuk membaca pesan untuk kontak yang sedang dipilih: ```js const message = state.messages[state.selectedId]; ``` -Here is the complete solution: +Inilah solusi lengkapnya: @@ -2237,19 +2238,19 @@ textarea { -Notably, you didn't need to change any of the event handlers to implement this different behavior. Without a reducer, you would have to change every event handler that updates the state. +Anda tidak perlu mengubah *event handler* mana pun untuk mengimplementasikan perilaku yang berbeda ini. Tanpa pengurang, Anda harus mengubah setiap *event handler* yang memperbarui *state*. -#### Implement `useReducer` from scratch {/*implement-usereducer-from-scratch*/} +#### Menerapkan `useReducer` dari awal {/*implement-usereducer-from-scratch*/} -In the earlier examples, you imported the `useReducer` Hook from React. This time, you will implement _the `useReducer` Hook itself!_ Here is a stub to get you started. It shouldn't take more than 10 lines of code. +Pada contoh sebelumnya, Anda mengimpor *Hook* `useReducer` dari React. Kali ini, Anda akan mengimplementasikan *Hook* `useReducer` itu sendiri! Berikut adalah kerangka kode untuk memulai. Ini tidak memerlukan lebih dari 10 baris kode. -To test your changes, try typing into the input or select a contact. +Untuk menguji perubahan Anda, coba ketik ke input atau pilih kontak. -Here is a more detailed sketch of the implementation: +Berikut adalah gambaran implementasi yang lebih detail: ```js export function useReducer(reducer, initialState) { @@ -2263,7 +2264,7 @@ export function useReducer(reducer, initialState) { } ``` -Recall that a reducer function takes two arguments--the current state and the action object--and it returns the next state. What should your `dispatch` implementation do with it? +Ingat bahwa sebuah fungsi *reducer* memiliki dua argumen--state saat ini dan objek action--dan mengembalikan state berikutnya. Apa yang seharusnya dilakukan oleh implementasi `dispatch` Anda dengannya? @@ -2439,7 +2440,7 @@ textarea { -Dispatching an action calls a reducer with the current state and the action, and stores the result as the next state. This is what it looks like in code: +Mengirimkan aksi akan memanggil *reducer* dengan state saat ini dan aksi tersebut, dan menyimpan hasilnya sebagai *state* berikutnya. Ini adalah contoh kodenya: @@ -2614,7 +2615,7 @@ textarea { -Though it doesn't matter in most cases, a slightly more accurate implementation looks like this: +Meskipun tidak terlalu penting dalam kebanyakan kasus, implementasi yang sedikit lebih akurat terlihat seperti ini: ```js function dispatch(action) { @@ -2622,7 +2623,7 @@ function dispatch(action) { } ``` -This is because the dispatched actions are queued until the next render, [similar to the updater functions.](/learn/queueing-a-series-of-state-updates) +Ini karena tindakan yang dipicu akan diantre sampai *render* berikutnya, serupa dengan fungsi *updater*. From c6aec0861f538d02ed8d26d78536d429991d5fe6 Mon Sep 17 00:00:00 2001 From: React Translation Bot <89879766+react-translations-bot@users.noreply.github.com> Date: Wed, 19 Jul 2023 08:55:32 +0100 Subject: [PATCH 004/675] Sync with react.dev @ a30e1f95 (#597) Co-authored-by: Brandon Dail Co-authored-by: Irfan Maulana --- src/content/reference/react/Component.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/reference/react/Component.md b/src/content/reference/react/Component.md index 006ade5d8d..569bf19f25 100644 --- a/src/content/reference/react/Component.md +++ b/src/content/reference/react/Component.md @@ -1393,7 +1393,7 @@ Then you can wrap a part of your component tree with it: If `Profile` or its child component throws an error, `ErrorBoundary` will "catch" that error, display a fallback UI with the error message you've provided, and send a production error report to your error reporting service. -You don't need to wrap every component into a separate error boundary. When you think about the [granularity of error boundaries,](https://aweary.dev/fault-tolerance-react/) consider where it makes sense to display an error message. For example, in a messaging app, it makes sense to place an error boundary around the list of conversations. It also makes sense to place one around every individual message. However, it wouldn't make sense to place a boundary around every avatar. +You don't need to wrap every component into a separate error boundary. When you think about the [granularity of error boundaries,](https://www.brandondail.com/posts/fault-tolerance-react) consider where it makes sense to display an error message. For example, in a messaging app, it makes sense to place an error boundary around the list of conversations. It also makes sense to place one around every individual message. However, it wouldn't make sense to place a boundary around every avatar. From b9eea4da28db66591ae5935898f98acdf009a0ad Mon Sep 17 00:00:00 2001 From: Ricky Date: Fri, 21 Jul 2023 19:24:36 -0400 Subject: [PATCH 005/675] Fix lint warnings (#6174) --- .eslintrc | 3 +- src/components/Layout/HomeContent.js | 32 +++++++------------ src/components/Layout/Sidebar/SidebarLink.tsx | 1 + .../Layout/SidebarNav/SidebarNav.tsx | 1 - src/components/Layout/TopNav/TopNav.tsx | 3 +- src/components/Layout/getRouteMeta.tsx | 2 +- src/components/MDX/BlogCard.tsx | 1 + src/components/MDX/MDXComponents.tsx | 3 +- src/components/MDX/Sandpack/CustomPreset.tsx | 1 - src/components/MDX/Sandpack/NavigationBar.tsx | 8 +++-- src/components/MDX/Sandpack/Preview.tsx | 1 - src/components/MDX/TeamMember.tsx | 1 - src/components/Search.tsx | 3 +- src/hooks/usePendingRoute.ts | 2 +- 14 files changed, 27 insertions(+), 35 deletions(-) diff --git a/.eslintrc b/.eslintrc index 147e54607e..f617dea267 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,8 @@ "plugins": ["@typescript-eslint"], "rules": { "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": "warn" + "@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_" }], + "react-hooks/exhaustive-deps": "error" }, "env": { "node": true, diff --git a/src/components/Layout/HomeContent.js b/src/components/Layout/HomeContent.js index ba3ff6ef14..e1b1b440dc 100644 --- a/src/components/Layout/HomeContent.js +++ b/src/components/Layout/HomeContent.js @@ -8,12 +8,10 @@ import { useState, useContext, useId, - Fragment, Suspense, useEffect, useRef, useTransition, - useReducer, } from 'react'; import cn from 'classnames'; import NextLink from 'next/link'; @@ -26,7 +24,6 @@ import {IconSearch} from 'components/Icon/IconSearch'; import {Logo} from 'components/Logo'; import Link from 'components/MDX/Link'; import CodeBlock from 'components/MDX/CodeBlock'; -import {IconNavArrow} from 'components/Icon/IconNavArrow'; import {ExternalLink} from 'components/ExternalLink'; import sidebarBlog from '../../sidebarBlog.json'; @@ -67,14 +64,6 @@ function Para({children}) { ); } -function Left({children}) { - return ( -
- {children} -
- ); -} - function Center({children}) { return (
@@ -90,19 +79,23 @@ function FullBleed({children}) { } function CurrentTime() { - const msPerMinute = 60 * 1000; - const date = new Date(); - let nextMinute = Math.floor(+date / msPerMinute + 1) * msPerMinute; - + const [date, setDate] = useState(new Date()); const currentTime = date.toLocaleTimeString([], { hour: 'numeric', minute: 'numeric', }); - let [, forceUpdate] = useReducer((n) => n + 1, 0); useEffect(() => { - const timeout = setTimeout(forceUpdate, nextMinute - Date.now()); + const msPerMinute = 60 * 1000; + let nextMinute = Math.floor(+date / msPerMinute + 1) * msPerMinute; + + const timeout = setTimeout(() => { + if (Date.now() > nextMinute) { + setDate(new Date()); + } + }, nextMinute - Date.now()); return () => clearTimeout(timeout); }, [date]); + return {currentTime}; } @@ -831,7 +824,7 @@ function ExampleLayout({ .filter((s) => s !== null); setOverlayStyles(nextOverlayStyles); } - }, [activeArea]); + }, [activeArea, hoverTopOffset]); return (
@@ -1211,7 +1204,7 @@ function useNestedScrollLock(ref) { window.removeEventListener('scroll', handleScroll); clearInterval(interval); }; - }, []); + }, [ref]); } function ExamplePanel({ @@ -1220,7 +1213,6 @@ function ExamplePanel({ noShadow, height, contentMarginTop, - activeArea, }) { return (
0 ? breadcrumbs : [routeTree], diff --git a/src/components/MDX/BlogCard.tsx b/src/components/MDX/BlogCard.tsx index ba610b1118..1a16013a20 100644 --- a/src/components/MDX/BlogCard.tsx +++ b/src/components/MDX/BlogCard.tsx @@ -18,6 +18,7 @@ function BlogCard({title, badge, date, icon, url, children}: BlogCardProps) { return (
diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx index ba531c9f03..8ddf371c9e 100644 --- a/src/components/MDX/MDXComponents.tsx +++ b/src/components/MDX/MDXComponents.tsx @@ -369,7 +369,8 @@ function YouTubeIframe(props: any) { } function Image(props: any) { - return ; + const {alt, ...rest} = props; + return {alt}; } export const MDXComponents = { diff --git a/src/components/MDX/Sandpack/CustomPreset.tsx b/src/components/MDX/Sandpack/CustomPreset.tsx index 6e08625645..46e0ca2554 100644 --- a/src/components/MDX/Sandpack/CustomPreset.tsx +++ b/src/components/MDX/Sandpack/CustomPreset.tsx @@ -54,7 +54,6 @@ export const CustomPreset = memo(function CustomPreset({ const SandboxShell = memo(function SandboxShell({ showDevTools, - onDevToolsLoad, devToolsLoaded, providedFiles, lintErrors, diff --git a/src/components/MDX/Sandpack/NavigationBar.tsx b/src/components/MDX/Sandpack/NavigationBar.tsx index 8c884a5d8a..94e2eb4b34 100644 --- a/src/components/MDX/Sandpack/NavigationBar.tsx +++ b/src/components/MDX/Sandpack/NavigationBar.tsx @@ -90,15 +90,17 @@ export function NavigationBar({providedFiles}: {providedFiles: Array}) { } else { return; } - }, [isMultiFile]); + + // Note: in a real useEvent, onContainerResize would be omitted. + }, [isMultiFile, onContainerResize]); const handleReset = () => { /** * resetAllFiles must come first, otherwise - * the previous content will appears for a second + * the previous content will appear for a second * when the iframe loads. * - * Plus, it should only prompts if there's any file changes + * Plus, it should only prompt if there's any file changes */ if ( sandpack.editorState === 'dirty' && diff --git a/src/components/MDX/Sandpack/Preview.tsx b/src/components/MDX/Sandpack/Preview.tsx index 2e140360c9..6b7a6d1836 100644 --- a/src/components/MDX/Sandpack/Preview.tsx +++ b/src/components/MDX/Sandpack/Preview.tsx @@ -49,7 +49,6 @@ export function Preview({ errorScreenRegisteredRef, openInCSBRegisteredRef, loadingScreenRegisteredRef, - status, } = sandpack; if ( diff --git a/src/components/MDX/TeamMember.tsx b/src/components/MDX/TeamMember.tsx index 887e9f6914..0db067e10e 100644 --- a/src/components/MDX/TeamMember.tsx +++ b/src/components/MDX/TeamMember.tsx @@ -7,7 +7,6 @@ import Image from 'next/image'; import {IconTwitter} from '../Icon/IconTwitter'; import {IconGitHub} from '../Icon/IconGitHub'; import {ExternalLink} from '../ExternalLink'; -import {IconNewPage} from 'components/Icon/IconNewPage'; import {H3} from './Heading'; import {IconLink} from 'components/Icon/IconLink'; diff --git a/src/components/Search.tsx b/src/components/Search.tsx index 2a9743ec39..8bc47297a1 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -5,11 +5,10 @@ import Head from 'next/head'; import Link from 'next/link'; import Router from 'next/router'; -import {lazy, useCallback, useEffect} from 'react'; +import {lazy, useEffect} from 'react'; import * as React from 'react'; import {createPortal} from 'react-dom'; import {siteConfig} from 'siteConfig'; -import cn from 'classnames'; export interface SearchProps { appId?: string; diff --git a/src/hooks/usePendingRoute.ts b/src/hooks/usePendingRoute.ts index 73ff0b8aff..229a36e64c 100644 --- a/src/hooks/usePendingRoute.ts +++ b/src/hooks/usePendingRoute.ts @@ -33,7 +33,7 @@ const usePendingRoute = () => { events.off('routeChangeComplete', handleRouteChangeComplete); clearTimeout(routeTransitionTimer); }; - }, []); + }, [events]); return pendingRoute; }; From d86cfc47634a3a3b94f683e99075b9815ac35a30 Mon Sep 17 00:00:00 2001 From: Ricky Date: Mon, 24 Jul 2023 10:05:08 -0400 Subject: [PATCH 006/675] Update useInsertionEffect docs (#6172) --- src/content/reference/react/useInsertionEffect.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/content/reference/react/useInsertionEffect.md b/src/content/reference/react/useInsertionEffect.md index 175b4476fa..0238facf7b 100644 --- a/src/content/reference/react/useInsertionEffect.md +++ b/src/content/reference/react/useInsertionEffect.md @@ -10,7 +10,7 @@ title: useInsertionEffect -`useInsertionEffect` is a version of [`useEffect`](/reference/react/useEffect) that fires before any DOM mutations. +`useInsertionEffect` allows inserting elements into the DOM before any layout effects fire. ```js useInsertionEffect(setup, dependencies?) @@ -26,7 +26,7 @@ useInsertionEffect(setup, dependencies?) ### `useInsertionEffect(setup, dependencies?)` {/*useinsertioneffect*/} -Call `useInsertionEffect` to insert the styles before any DOM mutations: +Call `useInsertionEffect` to insert styles before any effects fire that may need to read layout: ```js import { useInsertionEffect } from 'react'; @@ -44,7 +44,7 @@ function useCSS(rule) { #### Parameters {/*parameters*/} -* `setup`: The function with your Effect's logic. Your setup function may also optionally return a *cleanup* function. Before your component is added to the DOM, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. Before your component is removed from the DOM, React will run your cleanup function. +* `setup`: The function with your Effect's logic. Your setup function may also optionally return a *cleanup* function. When your component is added to the DOM, but before any layout effects fire, React will run your setup function. After every re-render with changed dependencies, React will first run the cleanup function (if you provided it) with the old values, and then run your setup function with the new values. When your component is removed from the DOM, React will run your cleanup function. * **optional** `dependencies`: The list of all reactive values referenced inside of the `setup` code. Reactive values include props, state, and all the variables and functions declared directly inside your component body. If your linter is [configured for React](/learn/editor-setup#linting), it will verify that every reactive value is correctly specified as a dependency. The list of dependencies must have a constant number of items and be written inline like `[dep1, dep2, dep3]`. React will compare each dependency with its previous value using the [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is) comparison algorithm. If you don't specify the dependencies at all, your Effect will re-run after every re-render of the component. @@ -56,8 +56,9 @@ function useCSS(rule) { * Effects only run on the client. They don't run during server rendering. * You can't update state from inside `useInsertionEffect`. -* By the time `useInsertionEffect` runs, refs are not attached yet, and DOM is not yet updated. - +* By the time `useInsertionEffect` runs, refs are not attached yet. +* `useInsertionEffect` may run either before or after the DOM has been updated. You shouldn't rely on the DOM being updated at any particular time. +* Unlike other types of Effects, which fire cleanup for every Effect and then setup for every Effect, `useInsertionEffect` will fire both cleanup and setup one component at a time. This results in an "interleaving" of the cleanup and setup functions. --- ## Usage {/*usage*/} @@ -87,7 +88,7 @@ If you use CSS-in-JS, we recommend a combination of the first two approaches (CS The first problem is not solvable, but `useInsertionEffect` helps you solve the second problem. -Call `useInsertionEffect` to insert the styles before any DOM mutations: +Call `useInsertionEffect` to insert the styles before any layout effects fire: ```js {4-11} // Inside your CSS-in-JS library From cf4ad1999a766f417bee506f11b37a6d05ac9342 Mon Sep 17 00:00:00 2001 From: Soichiro Miki Date: Tue, 25 Jul 2023 09:57:55 +0900 Subject: [PATCH 007/675] Fix typo: "server-only" to "client-only" (#6164) --- src/content/reference/react/Suspense.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/reference/react/Suspense.md b/src/content/reference/react/Suspense.md index 27add60356..dd93120558 100644 --- a/src/content/reference/react/Suspense.md +++ b/src/content/reference/react/Suspense.md @@ -2513,7 +2513,7 @@ However, now imagine you're navigating between two different user profiles. In t --- -### Providing a fallback for server errors and server-only content {/*providing-a-fallback-for-server-errors-and-server-only-content*/} +### Providing a fallback for server errors and client-only content {/*providing-a-fallback-for-server-errors-and-client-only-content*/} If you use one of the [streaming server rendering APIs](/reference/react-dom/server) (or a framework that relies on them), React will also use your `` boundaries to handle errors on the server. If a component throws an error on the server, React will not abort the server render. Instead, it will find the closest `` component above it and include its fallback (such as a spinner) into the generated server HTML. The user will see a spinner at first. From a132d8f35597d0380f0f738c1b095b5dc56736ba Mon Sep 17 00:00:00 2001 From: Soichiro Miki Date: Tue, 25 Jul 2023 09:59:14 +0900 Subject: [PATCH 008/675] Remove junk lines in common.md (#6167) --- src/content/reference/react-dom/components/common.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/content/reference/react-dom/components/common.md b/src/content/reference/react-dom/components/common.md index f1e4fcd0c5..fb0078906e 100644 --- a/src/content/reference/react-dom/components/common.md +++ b/src/content/reference/react-dom/components/common.md @@ -149,7 +149,6 @@ These standard DOM props are also supported for all built-in components: * [`onWheel`](https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event): A [`WheelEvent` handler](#wheelevent-handler) function. Fires when the user rotates a wheel button. * `onWheelCapture`: A version of `onWheel` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) * [`role`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles): A string. Specifies the element role explicitly for assistive technologies. -nt. * [`slot`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles): A string. Specifies the slot name when using shadow DOM. In React, an equivalent pattern is typically achieved by passing JSX as props, for example `} right={} />`. * [`spellCheck`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck): A boolean or null. If explicitly set to `true` or `false`, enables or disables spellchecking. * [`tabIndex`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex): A number. Overrides the default Tab button behavior. [Avoid using values other than `-1` and `0`.](https://www.tpgi.com/using-the-tabindex-attribute/) @@ -169,7 +168,6 @@ These events fire only for the [``](https://developer.mozilla.org/en-US/ * [`onCancel`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/cancel_event): An [`Event` handler](#event-handler) function. Fires when the user tries to dismiss the dialog. * `onCancelCapture`: A version of `onCancel` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) -capture-phase-events) * [`onClose`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/close_event): An [`Event` handler](#event-handler) function. Fires when a dialog has been closed. * `onCloseCapture`: A version of `onClose` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) @@ -177,7 +175,6 @@ These events fire only for the [`
`](https://developer.mozilla.org/en-US * [`onToggle`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDetailsElement/toggle_event): An [`Event` handler](#event-handler) function. Fires when the user toggles the details. * `onToggleCapture`: A version of `onToggle` that fires in the [capture phase.](/learn/responding-to-events#capture-phase-events) -capture-phase-events) These events fire for [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img), [` diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 11459b704c..7076e76f50 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -4,7 +4,7 @@ "routes": [ { "hasSectionHeader": true, - "sectionHeader": "react@18.2.0" + "sectionHeader": "react@{{version}}" }, { "title": "Overview", @@ -156,7 +156,7 @@ }, { "hasSectionHeader": true, - "sectionHeader": "react-dom@18.2.0" + "sectionHeader": "react-dom@{{version}}" }, { "title": "Hooks", diff --git a/src/siteConfig.js b/src/siteConfig.js index 0ada6b934d..6d37e10fd5 100644 --- a/src/siteConfig.js +++ b/src/siteConfig.js @@ -3,6 +3,7 @@ */ exports.siteConfig = { + version: '18.3.1', // -------------------------------------- // Translations should replace these lines: languageCode: 'en', diff --git a/vercel.json b/vercel.json index 957c250278..509d72b434 100644 --- a/vercel.json +++ b/vercel.json @@ -4,6 +4,50 @@ }, "trailingSlash": false, "redirects": [ + { + "source": "/", + "has": [ + { + "type": "host", + "value": "18.react.dev" + } + ], + "permanent": false, + "destination": "https://react.dev/" + }, + { + "source": "/", + "has": [ + { + "type": "host", + "value": "17.react.dev" + } + ], + "permanent": true, + "destination": "https://17.reactjs.org/" + }, + { + "source": "/", + "has": [ + { + "type": "host", + "value": "16.react.dev" + } + ], + "permanent": true, + "destination": "https://17.reactjs.org/" + }, + { + "source": "/", + "has": [ + { + "type": "host", + "value": "15.react.dev" + } + ], + "permanent": true, + "destination": "https://react-legacy.netlify.app/" + }, { "source": "/reference", "destination": "/reference/react", From 86d306f6cefbc2fd8e542df3b5959e86384129b9 Mon Sep 17 00:00:00 2001 From: Ricky Date: Mon, 29 Apr 2024 22:27:44 -0400 Subject: [PATCH 302/675] rm new redirects (#6816) --- vercel.json | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/vercel.json b/vercel.json index 509d72b434..957c250278 100644 --- a/vercel.json +++ b/vercel.json @@ -4,50 +4,6 @@ }, "trailingSlash": false, "redirects": [ - { - "source": "/", - "has": [ - { - "type": "host", - "value": "18.react.dev" - } - ], - "permanent": false, - "destination": "https://react.dev/" - }, - { - "source": "/", - "has": [ - { - "type": "host", - "value": "17.react.dev" - } - ], - "permanent": true, - "destination": "https://17.reactjs.org/" - }, - { - "source": "/", - "has": [ - { - "type": "host", - "value": "16.react.dev" - } - ], - "permanent": true, - "destination": "https://17.reactjs.org/" - }, - { - "source": "/", - "has": [ - { - "type": "host", - "value": "15.react.dev" - } - ], - "permanent": true, - "destination": "https://react-legacy.netlify.app/" - }, { "source": "/reference", "destination": "/reference/react", From 6d0aca18d59c0adaa7eae2e49e02ec174c47b037 Mon Sep 17 00:00:00 2001 From: E <2061889+bozoputer@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:42:35 -0400 Subject: [PATCH 303/675] Fix punctuation. (#6815) * Update typescript.md Fix punctuation. * Update src/content/learn/typescript.md --------- Co-authored-by: Ricky --- src/content/learn/typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/learn/typescript.md b/src/content/learn/typescript.md index 6f49c76567..7edf8eb6ed 100644 --- a/src/content/learn/typescript.md +++ b/src/content/learn/typescript.md @@ -137,7 +137,7 @@ The [`useState` Hook](/reference/react/useState) will re-use the value passed in const [enabled, setEnabled] = useState(false); ``` -Will assign the type of `boolean` to `enabled`, and `setEnabled` will be a function accepting either a `boolean` argument, or a function that returns a `boolean`. If you want to explicitly provide a type for the state, you can do so by providing a type argument to the `useState` call: +This will assign the type of `boolean` to `enabled`, and `setEnabled` will be a function accepting either a `boolean` argument, or a function that returns a `boolean`. If you want to explicitly provide a type for the state, you can do so by providing a type argument to the `useState` call: ```ts // Explicitly set the type to "boolean" From e538800cfecd4be204ebf52f0df4c1d658fc74e9 Mon Sep 17 00:00:00 2001 From: Strek Date: Tue, 30 Apr 2024 08:21:14 +0530 Subject: [PATCH 304/675] Update react-19.md (#6813) --- src/content/blog/2024/04/25/react-19.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/blog/2024/04/25/react-19.md b/src/content/blog/2024/04/25/react-19.md index 42490bcb72..f8559d3190 100644 --- a/src/content/blog/2024/04/25/react-19.md +++ b/src/content/blog/2024/04/25/react-19.md @@ -186,7 +186,7 @@ Actions are also integrated with React 19's new `
` features for `react-dom When a `` Action succeeds, React will automatically reset the form for uncontrolled components. If you need to reset the `` manually, you can call the new `requestFormReset` React DOM API. -For more information, see the `react-dom` docs for [``](/reference/react-dom/components/form), [``](/reference/react-dom/components/input), and [`
+
+ Logo by + + @sawaratsuki1004 + +
diff --git a/src/components/Layout/HomeContent.js b/src/components/Layout/HomeContent.js index e1fab6d713..6ae0cf3504 100644 --- a/src/components/Layout/HomeContent.js +++ b/src/components/Layout/HomeContent.js @@ -26,6 +26,7 @@ import Link from 'components/MDX/Link'; import CodeBlock from 'components/MDX/CodeBlock'; import {ExternalLink} from 'components/ExternalLink'; import sidebarBlog from '../../sidebarBlog.json'; +import * as React from 'react'; function Section({children, background = null}) { return ( @@ -115,12 +116,15 @@ export function HomeContent() { <>
+
+ +
-

+

React

@@ -489,7 +493,13 @@ export function HomeContent() {

- +
+ +
+
Welcome to the
React community diff --git a/src/components/Layout/TopNav/TopNav.tsx b/src/components/Layout/TopNav/TopNav.tsx index 021c735f8c..7139d8e1b7 100644 --- a/src/components/Layout/TopNav/TopNav.tsx +++ b/src/components/Layout/TopNav/TopNav.tsx @@ -249,16 +249,25 @@ export default function TopNav({ {isMenuOpen ? : }
- - - React - +
+ + + +
+
+ + + React + +
{ }} /> + +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, ""]] +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, ); +``` + +This will attach event listeners to the static server-generated HTML and make it interactive. + + + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + + + My app + + + ... + + ); +} +``` + +On the server, render `` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const {prelude} = await prerender(, { + bootstrapScripts: [assetMap['/main.js']] + }); + return new Response(prelude, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +Since your server is now rendering ``, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +async function handler(request) { + const {prelude} = await prerender(, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['/main.js']], + }); + return new Response(prelude, { + headers: { 'content-type': 'text/html' }, + }); +} +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline ` +``` + +On the client, your bootstrap script should [hydrate the entire `document` with a call to `hydrateRoot`:](/reference/react-dom/client/hydrateRoot#hydrating-an-entire-document) + +```js [[1, 4, ""]] +import { hydrateRoot } from 'react-dom/client'; +import App from './App.js'; + +hydrateRoot(document, ); +``` + +This will attach event listeners to the static server-generated HTML and make it interactive. + + + +#### Reading CSS and JS asset paths from the build output {/*reading-css-and-js-asset-paths-from-the-build-output*/} + +The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead of `styles.css` you might end up with `styles.123456.css`. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content. + +However, if you don't know the asset URLs until after the build, there's no way for you to put them in the source code. For example, hardcoding `"/styles.css"` into JSX like earlier wouldn't work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop: + +```js {1,6} +export default function App({ assetMap }) { + return ( + + + My app + + + ... + + ); +} +``` + +On the server, render `` and pass your `assetMap` with the asset URLs: + +```js {1-5,8,9} +// You'd need to get this JSON from your build tooling, e.g. read it from the build output. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', async (request, response) => { + const { prelude } = await prerenderToNodeStream(, { + bootstrapScripts: [assetMap['/main.js']] + }); + + response.setHeader('Content-Type', 'text/html'); + prelude.pipe(response); +}); +``` + +Since your server is now rendering ``, you need to render it with `assetMap` on the client too to avoid hydration errors. You can serialize and pass `assetMap` to the client like this: + +```js {9-10} +// You'd need to get this JSON from your build tooling. +const assetMap = { + 'styles.css': '/styles.123456.css', + 'main.js': '/main.123456.js' +}; + +app.use('/', async (request, response) => { + const { prelude } = await prerenderToNodeStream(, { + // Careful: It's safe to stringify() this because this data isn't user-generated. + bootstrapScriptContent: `window.assetMap = ${JSON.stringify(assetMap)};`, + bootstrapScripts: [assetMap['/main.js']], + }); + + response.setHeader('Content-Type', 'text/html'); + prelude.pipe(response); +}); +``` + +In the example above, the `bootstrapScriptContent` option adds an extra inline ` + + ); +} diff --git a/src/components/DevContentRefresher.tsx b/src/components/DevContentRefresher.tsx new file mode 100644 index 0000000000..31a0961ed8 --- /dev/null +++ b/src/components/DevContentRefresher.tsx @@ -0,0 +1,29 @@ +'use client'; + +import {useRouter} from 'next/navigation'; +import {useRef, useEffect} from 'react'; + +export function DevContentRefresher() { + const router = useRouter(); + const wsRef = useRef(null); + + useEffect(() => { + wsRef.current = new WebSocket('ws://localhost:3001'); + + wsRef.current.onmessage = (event) => { + const message = JSON.parse(event.data); + + if (message.event === 'refresh') { + console.log('Refreshing content...'); + // @ts-ignore + router.hmrRefresh(); // Triggers client-side refresh + } + }; + + return () => { + wsRef.current?.close(); + }; + }, [router]); + + return null; +} diff --git a/src/components/ErrorDecoderProvider.tsx b/src/components/ErrorDecoderProvider.tsx new file mode 100644 index 0000000000..bad1ed2d08 --- /dev/null +++ b/src/components/ErrorDecoderProvider.tsx @@ -0,0 +1,19 @@ +'use client'; + +import {ErrorDecoderContext} from './ErrorDecoderContext'; + +export function ErrorDecoderProvider({ + children, + errorMessage, + errorCode, +}: { + children: React.ReactNode; + errorMessage: string | null; + errorCode: string | null; +}) { + return ( + + {children} + + ); +} diff --git a/src/components/Layout/Feedback.tsx b/src/components/Layout/Feedback.tsx index 34db728ced..16b974c10d 100644 --- a/src/components/Layout/Feedback.tsx +++ b/src/components/Layout/Feedback.tsx @@ -3,12 +3,12 @@ */ import {useState} from 'react'; -import {useRouter} from 'next/router'; import cn from 'classnames'; +import {usePathname} from 'next/navigation'; export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) { - const {asPath} = useRouter(); - const cleanedPath = asPath.split(/[\?\#]/)[0]; + const pathname = usePathname(); + const cleanedPath = pathname.split(/[\?\#]/)[0]; // Reset on route changes. return ; } diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index 24d379589d..ea0c53e1cf 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -1,16 +1,16 @@ +'use client'; + /* * Copyright (c) Facebook, Inc. and its affiliates. */ -import {Suspense} from 'react'; import * as React from 'react'; -import {useRouter} from 'next/router'; +import {Suspense} from 'react'; import {SidebarNav} from './SidebarNav'; import {Footer} from './Footer'; import {Toc} from './Toc'; -// import SocialBanner from '../SocialBanner'; import {DocsPageFooter} from 'components/DocsFooter'; -import {Seo} from 'components/Seo'; + import PageHeading from 'components/PageHeading'; import {getRouteMeta} from './getRouteMeta'; import {TocContext} from '../MDX/TocContext'; @@ -20,8 +20,8 @@ import type {RouteItem} from 'components/Layout/getRouteMeta'; import {HomeContent} from './HomeContent'; import {TopNav} from './TopNav'; import cn from 'classnames'; -import Head from 'next/head'; +// Prefetch the code block component import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock'); interface PageProps { @@ -36,6 +36,7 @@ interface PageProps { }; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; languages?: Languages | null; + pathname: string; } export function Page({ @@ -44,11 +45,11 @@ export function Page({ routeTree, meta, section, + pathname, languages = null, }: PageProps) { - const {asPath} = useRouter(); - const cleanedPath = asPath.split(/[\?\#]/)[0]; - const {route, nextRoute, prevRoute, breadcrumbs, order} = getRouteMeta( + const cleanedPath = pathname.split(/[\?\#]/)[0]; + const {route, nextRoute, prevRoute, breadcrumbs} = getRouteMeta( cleanedPath, routeTree ); @@ -113,31 +114,17 @@ export function Page({ showSidebar = false; } - let searchOrder; - if (section === 'learn' || (section === 'blog' && !isBlogIndex)) { - searchOrder = order; - } - return ( <> - {(isHomePage || isBlogIndex) && ( - - - + // RSS Feed link is now handled by metadata in layout.tsx + )} - {/**/}
-
+
{content}
- {showToc && toc.length > 0 && } + {showToc && toc.length > 0 && }
diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index 72003df74f..f67b0ed2b4 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -5,12 +5,12 @@ import {useRef, useLayoutEffect, Fragment} from 'react'; import cn from 'classnames'; -import {useRouter} from 'next/router'; import {SidebarLink} from './SidebarLink'; import {useCollapse} from 'react-collapsed'; import usePendingRoute from 'hooks/usePendingRoute'; import type {RouteItem} from 'components/Layout/getRouteMeta'; import {siteConfig} from 'siteConfig'; +import {usePathname} from 'next/navigation'; interface SidebarRouteTreeProps { isForceExpanded: boolean; @@ -77,7 +77,7 @@ export function SidebarRouteTree({ routeTree, level = 0, }: SidebarRouteTreeProps) { - const slug = useRouter().asPath.split(/[\?\#]/)[0]; + const slug = usePathname().split(/[\?\#]/)[0]; const pendingRoute = usePendingRoute(); const currentRoutes = routeTree.routes as RouteItem[]; return ( diff --git a/src/components/Layout/Toc.tsx b/src/components/Layout/Toc.tsx index 5308c602ce..a8d2698987 100644 --- a/src/components/Layout/Toc.tsx +++ b/src/components/Layout/Toc.tsx @@ -11,7 +11,11 @@ export function Toc({headings}: {headings: Toc}) { // TODO: We currently have a mismatch between the headings in the document // and the headings we find in MarkdownPage (i.e. we don't find Recap or Challenges). // Select the max TOC item we have here for now, but remove this after the fix. - const selectedIndex = Math.min(currentIndex, headings.length - 1); + const selectedIndex = + currentIndex !== undefined + ? Math.min(currentIndex, headings.length - 1) + : -1; + return (