Memindahkan State ke Atas
These docs are old and won’t be updated. Go to react.dev for the new React docs.
These new documentation pages teach modern React and include live examples:
Seringkali, beberapa komponen perlu mencerminkan perubahan data yang sama. Kami menyarankan untuk memindah state
yang digunakan bersama tersebut ke modul parent terdekat yang dimiliki komponen-komponen terkait. Mari kita lihat bagaimana ini bekerja dalam aksi.
Di bagian ini, kita akan membuat kalkulator suhu yang menghitung apakah air akan mendidih pada suhu tertentu.
Kita akan memulai dengan komponen bernama BoilingVerdict
. Komponen tersebut akan menerima suhu celsius
sebagai prop, dan menentukan apakah suhu cukup untuk membuat air mendidih:
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>Air akan mendidih.</p>; }
return <p>Air tidak akan mendidih.</p>;}
Selanjutnya, kita akan menampilkan komponen bernama Calculator
. Komponen tersebut menampilkan <input>
yang memungkinkan Anda memasukkan suhu, dan menyimpan nilainya di this.state.temperature
.
Selain itu, ini menampilkan BoilingVerdict
untuk nilai masukan saat ini.
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''}; }
handleChange(e) {
this.setState({temperature: e.target.value}); }
render() {
const temperature = this.state.temperature; return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input value={temperature} onChange={this.handleChange} /> <BoilingVerdict celsius={parseFloat(temperature)} /> </fieldset>
);
}
}
Menambahkan Masukan Kedua
Syarat baru dari kita adalah selain masukan Celcius, kita juga memberikan masukan Fahrenheit, dan kedua masukan tersebut harus tetap sinkron.
Kita dapat memulai dengan mengeluarkan komponen TemperatureInput
dari Calculator
. Kita akan menambahkan prop scale
baru pada komponen tersebut yang dapat bernilai "c"
atau "f"
:
const scaleNames = { c: 'Celsius', f: 'Fahrenheit'};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale; return (
<fieldset>
<legend>Masukkan temperatur dalam skala {scaleNames[scale]}:</legend> <input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
Kita sekarang dapat mengubah Calculator
untuk membuat dua masukan suhu terpisah:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" /> <TemperatureInput scale="f" /> </div>
);
}
}
Kita memiliki dua masukan sekarang, tetapi ketika Anda memasukkan suhu di salah satunya, yang lain tidak diperbarui. Ini bertentangan dengan syarat kita: kita ingin tetap menyinkronkannya.
Kita juga tidak dapat menampilkan BoilingVerdict
dari Calculator
. Calculator
tidak tahu suhu saat ini karena suhu tersebut tersembunyi di dalam TemperatureInput
.
Menulis Fungsi Konversi
Pertama, kita akan menulis dua fungsi untuk mengkonversi suhu dari Celsius ke Fahrenheit dan sebaliknya:
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
Dua fungsi ini akan mengkonversi angka. Kita akan menulis fungsi lain yang menggunakan fungsi string temperature
dan fungsi konverter sebagai argumen dan mengembalikan string. Kita akan menggunakannya untuk menghitung nilai dari suatu masukan bersarakan masukan lainnya.
Ini mengembalikan string kosong pada temperature
yang tidak valid, dan itu membuat keluaran dibulatkan ke tempat desimal ketiga:
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
Misalnya, tryConvert('abc', toCelsius)
menghasilkan string kosong, dan tryConvert('10.22', toFahrenheit)
menghasilkan '50.396'
.
Memindahkan State ke Atas
Saat ini, kedua komponen TemperatureInput
secara independen menyimpan nilainya di state
lokal:
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''}; }
handleChange(e) {
this.setState({temperature: e.target.value}); }
render() {
const temperature = this.state.temperature; // ...
Namun, kita ingin kedua masukan selaras satu sama lain. Ketika kita memperbarui masukan Celcius, masukan Fahrenheit harus mencerminkan suhu yang dikonversi, dan sebaliknya.
Dalam React, state bersama dicapai dengan memindahkannya ke komponen induk bersama terdekat dari komponen yang membutuhkannya. Ini disebut “memindahkan state ke atas”. Kita akan menghapus state lokal dari TemperatureInput
dan memindahkannya ke Calculator
sebagai gantinya.
Jika Calculator
memiliki state bersama, state tersebut akan menjadi “sumber kebenaran” untuk suhu saat ini di kedua masukan. Ini dapat menginstruksikan kedua input untuk memiliki nilai yang konsisten satu sama lain. Karena kedua props
pada komponen TemperatureInput
berasal dari komponen Calculator
induk yang sama, kedua masukan akan selalu sinkron.
Mari kita lihat bagaimana ini bekerja selangkah demi selangkah.
Pertama, kita akan ganti this.state.temperature
dengan this.props.temperature
di komponen TemperatureInput
. Untuk saat ini, mari kita berpura-pura this.props.temperature
sudah ada, meskipun kita harus menampilkannya dari Calculator
:
render() {
// Before: const temperature = this.state.temperature;
const temperature = this.props.temperature; // ...
Kita tahu bahwa props bersifat read-only. Ketika temperature
berada di state lokal, TemperatureInput
bisa memanggil this.setState()
untuk mengubahnya. Namun, karena sekarang temperature
berasal dari induknya sebagai sebuah prop
, TemperatureInput
tidak memiliki kendali atasnya.
Dalam React, ini biasanya diselesaikan dengan membuat komponen “dikendalikan”. Sama seperti DOM <input>
menerima kedua value
dan onChange
prop, begitu juga dapat menyesuaikan TemperatureInput
menerima kedua temperature
dan onTemperatureChange
props dari Calculator
sebagai induknya.
Sekarang, ketika TemperatureInput
ingin memperbarui suhunya, ia memanggil this.props.onTemperatureChange
:
handleChange(e) {
// Before: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value); // ...
Catatan:
Tidak ada arti khusus untuk salah satu
temperature
atauonTemperatureChange
nama prop dalam komponen khusus. Kita bisa memanggil mereka apapun, seperti memberi nama merekavalue
danonChange
yang merupakan konvensi umum.
Prop onTemperatureChange
akan diberikan bersama-sama dengan prop temperature
oleh komponen induk Calculator
. Fungsi ini akan menangani perubahan dengan memodifikasi state lokalnya sendiri, sehingga menampilkan ulang kedua masukan dengan nilai-nilai baru. Kita akan segera melihat implementasi Calculator
yang baru.
Sebelum melihat perubahan di Calculator
, mari kita rekap perubahan kita ke komponen TemperatureInput
. Kita telah menghapus state lokal darinya, dan alih-alih membaca this.state.temperature
, kita sekarang membaca this.props.temperature
. Alih-alih memanggil this.setState()
saat kita ingin melakukan perubahan, kita sekarang memanggil this.props.onTemperatureChange()
, yang akan disediakan oleh Calculator
:
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value); }
render() {
const temperature = this.props.temperature; const scale = this.props.scale;
return (
<fieldset>
<legend>Masukkan temperatur dalam skala {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
Sekarang mari kita beralih ke komponen Calculator
.
Kita akan menyimpan masukan temperature
saat ini dan scale
dalam state lokal. Ini adalah kondisi yang kita “angkat” dari masukan, dan itu akan berfungsi sebagai “sumber kebenaran” untuk keduanya. Ini adalah representasi minimal dari semua data yang perlu kita ketahui untuk membuat kedua masukan.
Misalnya, jika kita memasukkan 37 ke dalam masukan Celcius, state pada komponen Calculator
akan menjadi:
{
temperature: '37',
scale: 'c'
}
Jika nanti kita mengubah kolom Fahrenheit menjadi 212, state pada Calculator
akan menjadi:
{
temperature: '212',
scale: 'f'
}
Kita bisa menyimpan nilai dari kedua masukan tetapi tidak perlu. Cukup untuk menyimpan nilai masukan yang terakhir diubuah, dan skala yang diwakilinya. Kita kemudian dapat menyimpulkan nilai masukan lain berdasarkan alur temperature
dan scale
sendiri.
Masukan tetap sinkron karena nilainya dihitung dari state yang sama:
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'}; }
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature}); }
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature}); }
render() {
const scale = this.state.scale; const temperature = this.state.temperature; const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature; const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius} onTemperatureChange={this.handleCelsiusChange} /> <TemperatureInput
scale="f"
temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange} /> <BoilingVerdict
celsius={parseFloat(celsius)} /> </div>
);
}
}
Sekarang, tidak peduli apabila masukan berubah, this.state.temperature
dan this.state.scale
dalam Calculator
akan diperbarui. Salah satu masukan mendapatkan nilai apa adanya, sehingga setiap masukan pengguna dipertahankan, dan nilai masukan lainnya selalu dihitung ulang berdasarkan masukan.
Mari kita rekap apa yang terjadi ketika Anda mengubah masukan:
- React memanggil fungsi yang disebut
onChange
pada DOM<input>
. Dalam kasus kita, ini adalah metodehandleChange
dalam komponenTemperatureInput
. - Metode
handleChange
dalam komponenTemperatureInput
memanggilthis.props.onTemperatureChange()
dengan nilai baru. Itu merupakan props, termasukonTemperatureChange
, disediakan oleh komponen induknya,Calculator
. - Ketika sebelumnya di-render,
Calculator
telah menentukan bahwaonTemperatureChange
dariTemperatureInput
Celcius adalah metodehandleCelsiusChange
milikCalculator
, danonTemperatureChange
dariTemperatureInput
Fahrenheit adalah metodehandleFahrenheitChange
milikCalculator
. Jadi salah satu dari dua metodeCalculator
dipanggil tergantung pada masukan yang kita ubah. - Di dalam metode ini, komponen
Calculator
meminta React untuk mengulang render dengan memanggilthis.setState()
dengan nilai masukan baru dan skala masukan saat ini yang baru saja kita ubah. - React memanggil komponen
Calculator
metoderender
untuk mempelajari seperti apa tampilan antarmuka pengguna. Nilai kedua masukan dihitung berdasarkan suhu saat ini dan skala aktif. Konversi suhu dilakukan di sini. - React memanggil metode
render
pada komponenTemperatureInput
individual dengan props baru yang ditentukan olehCalculator
. Mreka kemduain mempelajari seperti apa tampilan antarmuka pengguna mereka. - React memanggil metode
render
pada komponenBoilingVerdict
, melewati suhu dalam Celcius sebagai props. - React DOM memperbarui dengan vonis didih dan untuk mencocokan nilai masukan yang diinginkan. Masukan yang baru saja kita ubah menerima nilainya saat ini, dan masukan lainnya diperbarui ke suhu setelah konversi.
Setiap pembaruan melewati langkah yang sama sehingga masukan tetap sinkron.
Pelajaran yang Dipetik
Seharusnya ada “sumber kebenaran” tunggal untuk setiap data yang berubah dalam aplikasi React. Biasanya, state ditambahkan pertama kali ke komponen yang membutuhkannya untuk di-render. Kemudian, jika komponen lain juga membutuhkannya, Anda dapat memindahkan state ke komponen induk bersama terdekat. Alih-alih mencoba menyinkronkan state antara komponen yang berbeda, Anda harus mengandalkan aliran data dari atas kebawah.
Pengangkatan state melibatkan penulisan lebih banyak kode ”boilerplate” daripada pendekatan pengikatan dua arah, tetapi sebagai manfaat, dibutuhkan lebih sedikit pekerjaan untuk menemukan dan mengisolasi bug. Karena tiap state akan “hidup” di sebuah komponen dan hanya komponen itu sendiri yang dapat mengubahnya, area permukaan untuk bug akan sangat berkurang. Selain itu, Anda dapat menerapkan logika khusus apa pun untuk menolak atau mengubah masukan pengguna.
Jika sesuatu dapat diturunkan dari props atau state, hal itu mungkin tidak sebaiknya berada di state. Sebagai contoh, alih-alih menyimpan keduanya celsiusValue
dan fahrenheitValue
, kita menyimpan hanya temperature
yang terakhir diubah dan scale
-nya. Nilai dari masukan lain selalu dapat dihitung dari kedua nilai tersebut dalam metode render()
. Ini memungkinkan kita menghapus atau menerapkan pembulatan ke masukan lain tanpa kehilangan ketepatan pada masukan pengguna.
Ketika Anda melihat sesuatu yang salah di antarmuka pengguna, Anda dapat menggunakan React Developer Tools untuk memeriksa props dan menelusuri tree komponen Anda ke atas sampai Anda menemukan komponen yang bertanggung jawab untuk memperbarui state. Ini memungkinkan Anda melacak bug ke sumbernya: