Berbagi State Antar Komponen
Terkadang, Anda ingin 2 komponen selalu berubah secara bersamaan. Untuk melakukannya, hapus state dari kedua komponen, pindahkan ke komponen induk terdekat, dan kemudian oper ke komponen tersebut melalui props. Ini dikenal sebagai lifting state up, dan ini adalah salah satu hal yang paling umum yang akan Anda lakukan saat menulis kode React.
You will learn
- Bagaimana cara berbagi state antar komponen dengan lifting state up
- Apa itu komponen terkendali dan tidak terkendali
Contoh Lifting State Up
Pada contoh ini, komponen induk Accordion
merender dua komponen Panel
terpisah:
Accordion
Panel
Panel
Setiap komponen Panel
memiliki state boolean isActive
yang menentukan apakah kontennya terlihat.
Tekan tombol Tampilkan untuk kedua panel:
import { useState } from 'react'; function Panel({ title, children }) { const [isActive, setIsActive] = useState(false); return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Tampilkan </button> )} </section> ); } export default function Accordion() { return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="Tentang"> Dengan populasi sekitar 2 juta, Almaty adalah kota terbesar di Kazakhstan. Dari tahun 1929 hingga 1997, itu adalah ibu kota negara tersebut. </Panel> <Panel title="Etimologi"> Nama berasal dari <span lang="kk-KZ">алма</span>, kata Kazakh untuk "apel" dan sering diterjemahkan sebagai "penuh dengan apel". Faktanya, wilayah sekitar Almaty dipercaya sebagai rumah leluhur apel, dan <i lang="la">Malus sieversii</i> si liar dianggap sebagai kandidat yang mungkin untuk leluhur apel domestik modern. </Panel> </> ); }
Perhatikan bagaimana menekan tombol satu panel tidak memengaruhi panel lainnya - mereka independen.
Tetapi sekarang katakanlah Anda ingin mengubah panel tersebut sehingga hanya satu panel yang dibuka pada satu waktu. Dengan desain diatas, membuka panel kedua harus menutup panel pertama. Bagaimana Anda melakukannya?
Untuk mengkoordinasikan kedua panel ini, Anda perlu “mengangkat state mereka” ke komponen induk dalam tiga langkah:
- Hapus state dari komponen anak.
- Oper data dari komponen induk.
- Tambahkan state ke komponen induk dan oper bersamaan dengan Event Handlers.
Cara ini akan memungkinkan komponen Accordion
untuk mengkoordinasikan kedua Panel
dan hanya membuka satu panel pada satu waktu.
Langkah 1: Hapus state dari komponen anak
Anda akan memberikan kontrol isActive
dari Panel
ke komponen induknya. Ini berarti komponen induk akan mengoper isActive
ke Panel
sebagai prop. Mulai dengan menghapus baris ini dari komponen Panel
:
const [isActive, setIsActive] = useState(false);
Lalu, tambahkan isActive
ke daftar prop Panel
:
function Panel({ title, children, isActive })
Sekarang komponen induk Panel
dapat mengontrol isActive
dengan mengoper sebagai prop. Sebaliknya, komponen Panel
sekarang tidak memiliki kontrol atas nilai isActive
- sekarang terserah komponen induk!
Langkah 2: Oper data yang diperlukan dari komponen induk
Untuk mengangkat state, Anda harus menemukan komponen induk yang paling dekat dari kedua komponen anak yang ingin Anda koordinasikan:
Accordion
(Komponen induk terdekat)Panel
Panel
Pada contoh ini, komponen Accordion
adalah yang terdekat. Karena komponen ini berada di atas kedua panel dan dapat mengontrol prop mereka, komponen ini akan menjadi “sumber kebenaran” untuk panel mana yang sedang aktif. Buat komponen Accordion
mengoper nilai isActive
yang telah ditentukan sebelumnya (misalnya, true
) ke kedua panel:
import { useState } from 'react'; export default function Accordion() { return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="Tentang" isActive={true}> Dengan populasi sekitar 2 juta, Almaty adalah kota terbesar di Kazakhstan. Dari tahun 1929 hingga 1997, itu adalah ibu kota negara tersebut. </Panel> <Panel title="Etimologi" isActive={true}> Nama berasal dari <span lang="kk-KZ">алма</span>, kata Kazakh untuk "apel" dan sering diterjemahkan sebagai "penuh dengan apel". Faktanya, wilayah sekitar Almaty dipercaya sebagai rumah leluhur apel, dan <i lang="la">Malus sieversii</i> si liar dianggap sebagai kandidat yang mungkin untuk leluhur apel domestik modern. </Panel> </> ); } function Panel({ title, children, isActive }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Tampilkan </button> )} </section> ); }
Coba edit nilai isActive
yang telah ditentukan sebelumnya di komponen Accordion
dan lihat hasilnya di layar.
Langkah 3: Tambahkan state ke komponen induk
Memindahkan state keatas sering mengubah sifat apa yang Anda simpan sebagai state.
Dalam contoh ini, Anda ingin mengubah Accordion
sehingga hanya satu panel yang dapat dibuka pada satu waktu. Ini berarti bahwa komponen induk Accordion
perlu melacak panel mana yang sedang aktif. Alih-alih nilai boolean
, ini dapat menggunakan angka sebagai indeks Panel
aktif untuk variabel state:
const [activeIndex, setActiveIndex] = useState(0);
Ketika activeIndex
bernilai 0
, panel pertama aktif, dan ketika bernilai 1
, panel kedua aktif.
Menekan tombol “Tampilkan” di salah satu Panel
perlu mengubah indeks aktif di Accordion
. Sebuah Panel
tidak dapat mengatur state activeIndex
secara langsung karena didefinisikan di dalam Accordion
. Komponen Accordion
perlu mengeksplisitkan komponen Panel
untuk mengubah state-nya dengan mengoper event handler sebagai prop:
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
The <button>
inside the Panel
will now use the onShow
prop as its click event handler:
import { useState } from 'react'; export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0); return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="About" isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="Etymology" isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); } function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Show </button> )} </section> ); }
This completes lifting state up! Moving state into the common parent component allowed you to coordinate the two panels. Using the active index instead of two “is shown” flags ensured that only one panel is active at a given time. And passing down the event handler to the child allowed the child to change the parent’s state.
Deep Dive
It is common to call a component with some local state “uncontrolled”. For example, the original Panel
component with an isActive
state variable is uncontrolled because its parent cannot influence whether the panel is active or not.
In contrast, you might say a component is “controlled” when the important information in it is driven by props rather than its own local state. This lets the parent component fully specify its behavior. The final Panel
component with the isActive
prop is controlled by the Accordion
component.
Uncontrolled components are easier to use within their parents because they require less configuration. But they’re less flexible when you want to coordinate them together. Controlled components are maximally flexible, but they require the parent components to fully configure them with props.
In practice, “controlled” and “uncontrolled” aren’t strict technical terms—each component usually has some mix of both local state and props. However, this is a useful way to talk about how components are designed and what capabilities they offer.
When writing a component, consider which information in it should be controlled (via props), and which information should be uncontrolled (via state). But you can always change your mind and refactor later.
A single source of truth for each state
In a React application, many components will have their own state. Some state may “live” close to the leaf components (components at the bottom of the tree) like inputs. Other state may “live” closer to the top of the app. For example, even client-side routing libraries are usually implemented by storing the current route in the React state, and passing it down by props!
For each unique piece of state, you will choose the component that “owns” it. This principle is also known as having a “single source of truth”. It doesn’t mean that all state lives in one place—but that for each piece of state, there is a specific component that holds that piece of information. Instead of duplicating shared state between components, lift it up to their common shared parent, and pass it down to the children that need it.
Your app will change as you work on it. It is common that you will move state down or back up while you’re still figuring out where each piece of the state “lives”. This is all part of the process!
To see what this feels like in practice with a few more components, read Thinking in React.
Recap
- When you want to coordinate two components, move their state to their common parent.
- Then pass the information down through props from their common parent.
- Finally, pass the event handlers down so that the children can change the parent’s state.
- It’s useful to consider components as “controlled” (driven by props) or “uncontrolled” (driven by state).
Challenge 1 of 2: Synced inputs
These two inputs are independent. Make them stay in sync: editing one input should update the other input with the same text, and vice versa.
import { useState } from 'react'; export default function SyncedInputs() { return ( <> <Input label="First input" /> <Input label="Second input" /> </> ); } function Input({ label }) { const [text, setText] = useState(''); function handleChange(e) { setText(e.target.value); } return ( <label> {label} {' '} <input value={text} onChange={handleChange} /> </label> ); }