Controlled Component

Ketika Anda membuat form pada aplikasi web, biasanya state atau data input berada di dalam DOM. Namun di React, manajemen state (data) sangat efektif bila dilakukan di dalam komponen. Sehingga, data dalam mengelola nilai form sebaiknya tidak dilakukan di DOM, melainkan di dalam komponen dengan memanfaatkan state. Hal ini membuat state di dalam form dapat lebih terkontrol.

Controlled Component merupakan component yang me-render form, tetapi “single source of truth” atau sumber datanya diambil dari component state, bukan DOM. Alasan mengapa disebut dengan controlled component karena React mengontrol data form.

Contoh, di sini kita memiliki komponen yang me-render form dengan satu input.

import React from 'react';
 
class NameForm extends React.Component {
  constructor(props) {
    super(props);
 
    this.state = {
      email: ''
    };
  }
 
  render() {
    return (
      <form>
        <input type="text" value={this.state.email} />
      </form>
    );
  }
}

Pada element input, kita memberikan properti value dengan nilai yang berasal dari state (email). Itu berarti, nilai input akan selalu sama dengan nilai state email. Karena itu, satu-satunya cara memperbarui nilai input adalah memperbarui nilai component state. Ini adalah contoh dari controlled element, di mana React yang mengontrol nilai dari input form.

Untuk mengubah nilai state email, kita perlu membuat handler seperti ini.

import React from 'react';
 
class NameForm extends React.Component {
  constructor(props) {
    super(props);
 
    this.state = {
      email: ''
    };
 
    this.onEmailChangeHandler = this.onEmailChangeHandler.bind(this);
  }
 
  onEmailChangeHandler(event) {
    this.setState(() => {
      return {
        email: event.target.value
      };
    });
  }
 
  render() {
    return (
      <form>
        <input 
        type="text"
        value={this.state.email}
        onChange={this.onEmailChangeHandler} />
      </form>
    );
  }
}

Kapan pun input berubah, fungsi handler akan dipanggil karena kita menetapkan fungsi tersebut pada property onChange.

Walau dalam membuat controlled component banyak kode yang perlu ditulis, tapi ada kelebihannya juga, lho! Salah satunya, kita dapat menerapkan validasi secara instan.Karena state dikelola oleh component, kita bisa menuliskan logika validasi di component dan validasi akan dijalankan setiap kali nilai state berubah.

Ketahuilah bahwa benefit tersebut terjadi karena adanya input dari pengguna. Jika ada input, state akan berubah, dan UI akan diperbarui berdasarkan state terbaru. Ini adalah konsep inti, bukan hanya pada controlled component, tetapi React secara umum.


Latihan Membuat Controlled Component

Di latihan kali ini, kita akan belajar bagaimana membuat dan mengelola nilai pada Form menggunakan React. Tujuannya agar Anda paham cara kerja controlled component di mana state pada Form dikontrol atau diatur oleh React bukan DOM.

Berikut tampilan Form sederhana yang akan kita buat di latihan kali ini.


Yuk, kita mulai!

  1. Buka tautan dicoding-react-starter atau Anda juga boleh membuat proyek react baru secara lokal menggunakan CRA.
  2. Kemudian pada berkas index.js, buat component baru bernama MyForm dan bangun UI Form sesuai dengan gambar di atas. Silakan tulis kode di bawah ini. Kami sarankan Anda untuk tidak copy-paste agar kemampuan menuliskan JSX Anda lebih terasah.

    import React from 'react';
    import { createRoot } from 'react-dom/client';
     
    class MyForm extends React.Component {
      render() {
        return (
          <div>
            <h1> Register Form</h1>
            <form>
              <label for="name">Name: </label>
              <input id="name" type="text" />
              <br />
              <label for="email">Email: </label>
              <input id="email" type="text" />
              <br />
              <label for="gender">Gender: </label>
              <select id="gender">
                <option value="Man">Man</option>
                <option value="Woman">Woman</option>
              </select>
              <br />
              <button type="submit">submit</button>
            </form>
          </div>
        );
      }
    }
     
    const root = createRoot(document.getElementById('root'));
    root.render(<MyForm />);
    

  3. Simpan perubahan kode dan browser akan menampilkan UI seperti ini.


    Saat ini, Anda bisa berinteraksi dengan form tersebut, seperti menuliskan nilai pada input name, email, atau memilih gender. Namun, ketahuilah bahwa data atau state dari component MyForm tidak dikontrol (uncontrolled) oleh React karena data masih disimpan di dalam DOM. Sulit untuk mendapatkan nilai input pada uncontrolled component karena kita perlu berinteraksi langsung dengan DOM. Namun, sulit bukan berarti tidak bisa, ya! Jika ingin mengetahui caranya, Anda bisa mencari tahu melalui dokumentasi yang diberikan React tentang Uncontrolled Component.

  4. Agar nilai state dari Form dapat dikontrol oleh React, kita perlu menyiapkan nilai state pada masing-masing input yang ada. Silakan inisialisasi state untuk nilai name, email, dan gender pada constructor.

    import React from 'react';
    import { createRoot } from 'react-dom/client';
     
    class MyForm extends React.Component {
      constructor(props) {
        super(props);
     
        // inisialisasi state
        this.state = {
          name: '',
          email: '',
          gender: 'Man'
        };
      }
     
      // fungsi render disembunyikan
    }
     
    const root = createRoot(document.getElementById('root'));
    root.render(<MyForm />);
    

  5. Setelah menyiapkan state, beri nilai state pada masing-masing <input> dan <select> melalui props value pada fungsi render.

    import React from 'react';
    import { createRoot } from 'react-dom/client';
     
    class MyForm extends React.Component {
      // kode constructor disembunyikan
     
      render() {
        return (
          <div>
            <h1> Register Form</h1>
            <form>
              <label for="name">Name: </label>
              <input id="name" type="text" value={this.state.name} />
              <br />
              <label for="email">Email: </label>
              <input id="email" type="text" value={this.state.email} />
              <br />
              <label for="gender">Gender: </label>
              <select id="gender" value={this.state.gender}>
                <option value="Man">Man</option>
                <option value="Woman">Woman</option>
              </select>
              <br />
              <button type="submit">submit</button>
            </form>
          </div>
        );
      }
    }
     
    const root = createRoot(document.getElementById('root'));
    root.render(<MyForm />);
    

  6. Simpan perubahan kode dan coba interaksikan kembali Form yang tampak pada browser. Pasti nilai dari Form tersebut tidak bisa berubah ‘kan?


    Sekarang, nilai (value) dari tiap <input> selalu menggunakan nilai dari component state. Oleh karena itu, tidak ada cara lagi untuk mengubah nilai value pada input selain mengubah nilai component state-nya.

  7. Kita akan mengubah nilai state setiap kali terjadi input pengguna pada <input>. Oleh karena itu, siapkan event handler terlebih dahulu guna menangani perubahan stateuntuk masing-masing input.

    import React from 'react';
    import { createRoot } from 'react-dom/client';
     
    class MyForm extends React.Component {
      constructor(props) {
        super(props);
     
        // inisialisasi state
        this.state = {
          name: '',
          email: '',
          gender: 'Man'
        };
     
        // binding this context to event handler
        this.onNameChangeEventHandler = this.onNameChangeEventHandler.bind(this);
        this.onEmailChangeEventHandler = this.onEmailChangeEventHandler.bind(this);
        this.onGenderChangeEventHandler = this.onGenderChangeEventHandler.bind(this);
      }
     
      onNameChangeEventHandler(event) {
        this.setState(() => {
          return {
            name: event.target.value
          };
        });
      }
     
      onEmailChangeEventHandler(event) {
        this.setState(() => {
          return {
            email: event.target.value
          };
        });
      }
     
      onGenderChangeEventHandler(event) {
        this.setState((prevState) => {
          return {
            gender: event.target.value
          };
        });
      }
     
      // fungsi render disembunyikan
    }
     
    const root = createRoot(document.getElementById('root'));
    root.render(<MyForm />);
    

    Lihat kode di dalam masing-masing event handler, di sana kita mengubah nilai state sesuai dengan input yang diberikan.

  8. Selanjutnya, gunakan event handler pada masing-masing elemen <input> melalui props onChange. Sehingga, fungsi event handler akan dijalankan dan mengubah nilai state setiap kali ada perubahan data yang dilakukan pengguna pada elemen <input> (termasuk <select>).

    import React from 'react';
    import { createRoot } from 'react-dom/client';
     
    class MyForm extends React.Component {
     // … constructor dan event handler disembunyikan
     
     render() {
       return (
         <div>
           <h1> Register Form</h1>
           <form>
             <label for="name">Name: </label>
             <input id="name" type="text" value={this.state.name} onChange={this.onNameChangeEventHandler} />
             <br />
             <label for="email">Email: </label>
             <input id="email" type="text" value={this.state.email} onChange={this.onEmailChangeEventHandler} />
             <br />
             <label for="gender">Gender: </label>
             <select id="gender" value={this.state.gender} onChange={this.onGenderChangeEventHandler}>
               <option value="Man">Man</option>
               <option value="Woman">Woman</option>
             </select>
             <br />
             <button type="submit">submit</button>
           </form>
         </div>
       );
     }
    }
     
    const root = createRoot(document.getElementById('root'));
    root.render(<MyForm />);
    

  9. Simpan perubahan dan coba interaksikan Form-nya kembali. Seharusnya nilai dari Form dapat berubah berdasarkan input yang kita berikan.


    Kini state dari Form sepenuhnya sudah dikontrol oleh React. Seluruh nilai yang tampak pada elemen input dapat Anda akses melalui this.state.

  10. Terakhir, kita akan mengaktifkan tombol submit untuk menampilkan alert sesuai data yang telah kita masukkan pada elemen <input>. Silakan buat event handler baru dengan nama onSubmitEventHandler, kemudian tuliskan kode berikut di dalamnya.

    import React from 'react';
    import { createRoot } from 'react-dom/client';
     
    class MyForm extends React.Component {
     constructor(props) {
       super(props);
     
       // inisialisasi state
       this.state = {
         name: '',
         email: '',
         gender: 'Man'
       };
     
       // binding this context to event handler
       this.onNameChangeEventHandler = this.onNameChangeEventHandler.bind(this);
       this.onEmailChangeEventHandler = this.onEmailChangeEventHandler.bind(this);
       this.onGenderChangeEventHandler = this.onGenderChangeEventHandler.bind(this);
       this.onSubmitEventHandler = this.onSubmitEventHandler.bind(this);
     }
     
     // .. fungsi handler lain disembunyikan
     
     onSubmitEventHandler(event) {
       // menghentikan aksi default dari tombol submit
       event.preventDefault();
     
       const message = `
         Name: ${this.state.name}
         Email: ${this.state.email}
         Gender: ${this.state.gender}
       `;
     
       alert(message);
     }
     
     
     // .. fungsi render disembunyikan
    }
     
    const root = createRoot(document.getElementById('root'));
    root.render(<MyForm />);
    

    Catatan: Jangan lupa binding method onSubmitEventHandler seperti yang kami lakukan pada constructor.

    Kemudian terapkan event handler tersebut pada element <form> melalui props onSubmit.

    import React from 'react';
    import { createRoot } from 'react-dom/client';
     
    class MyForm extends React.Component {
      // constructor dan event handler disembunyikan
     
      render() {
        return (
          <div>
            <h1> Register Form</h1>
            <form onSubmit={this.onSubmitEventHandler}>
              <label for="name">Name: </label>
              <input id="name" type="text" value={this.state.name} onChange={this.onNameChangeEventHandler} />
              <br />
              <label for="email">Email: </label>
              <input id="email" type="text" value={this.state.email} onChange={this.onEmailChangeEventHandler} />
              <br />
              <label for="gender">Gender: </label>
              <select id="gender" value={this.state.gender} onChange={this.onGenderChangeEventHandler}>
                <option value="Man">Man</option>
                <option value="Woman">Woman</option>
              </select>
              <br />
              <button type="submit">submit</button>
            </form>
          </div>
        );
      }
    }
     
    const root = createRoot(document.getElementById('root'));
    root.render(<MyForm />);
    

    Simpan perubahan kode tersebut, interaksikan Form, dan submit datanya! Seharusnya aplikasi sudah berjalan sesuai yang kita harapkan.

Tambahan materi tentang controlled component dapat Anda baca di sini.

React Form


Latihan Studi Kasus: Menambahkan Fitur Tambah Kontak

Selamat datang kembali di latihan studi kasus! Setelah berhasil membangun fitur menghapus kontak, selanjutnya kita akan membuat fitur untuk menambah kontak. Pas sekali dengan materi yang baru saja Anda pelajari, bukan? Yup! Kita akan mempraktikkan lagi pembuatan controlled component dalam membangun fitur tambah kontak.

Di akhir latihan ini, aplikasi kontak akan tampak seperti ini.


Yuk, kita mulai latihannya!

  1. Silakan buka kembali proyek contacts-app pada VSCode.
  2. Kemudian buat berkas baru JavaScript bernama ContactInput.js di dalam folder src -> components.


  3. Di dalamnya, buat class component bernama ContactInputdan bangun UI-nya menggunakan JSX seperti ini.
    import React from 'react';
     
    class ContactInput extends React.Component {
     render() {
       return (
         <form className='contact-input'>
           <input type="text" placeholder="Nama" />
           <input type="text" placeholder="Tag" />
           <button type="submit">Tambah</button>
         </form>
       )
     }
    }
     
    export default ContactInput;
    

  4. Karena kita membangun controlled component, jadi siapkan juga state dan event handler untuk masing-masing input (termasuk tombol Tambah).
    import React from 'react';
     
    class ContactInput extends React.Component {
     constructor(props) {
       super(props);
     
       // inisialisasi state
       this.state = {
         name: '',
         tag: '',
       }
     
       this.onNameChangeEventHandler = this.onNameChangeEventHandler.bind(this);
       this.onTagChangeEventHandler = this.onTagChangeEventHandler.bind(this);
       this.onSubmitEventHandler = this.onSubmitEventHandler.bind(this);
     }
     
     onNameChangeEventHandler(event) {
       this.setState(() => {
         return {
           name: event.target.value,
         }
       });
     }
     
     onTagChangeEventHandler(event) {
       this.setState(() => {
         return {
           tag: event.target.value,
         }
       });
     }
     
     onSubmitEventHandler(event) {
       event.preventDefault();
       this.props.addContact(this.state);
     }
     
     // fungsi render disembunyikan
    }
     
    export default ContactInput;
    

    Kodenya sama seperti latihan sebelumnya, bukan? Bedanya hanya pada handler onSubmitEventHandler. Di sana kita memanggil fungsi addContact dengan membawa nilai state karena fungsi untuk menambahkan (mengubah) data harus berada pada component yang memiliki data tersebut sehingga fungsi addContact di component ini dikirimkan melalui props.
  5. Selanjutnya, berikan nilai state pada props value; dan event handler pada props onChange dan onSubmit untuk masing-masing element <input> dan <form>.
    import React from 'react';
     
    class ContactInput extends React.Component {
     // constructor dan handler disembunyikan
     
     render() {
       return (
         <form className='contact-input' onSubmit={this.onSubmitEventHandler}>
           <input type="text" placeholder="Nama" value={this.state.name} onChange={this.onNameChangeEventHandler} />
           <input type="text" placeholder="Tag" value={this.state.tag} onChange={this.onTagChangeEventHandler} />
           <button type="submit">Tambah</button>
         </form>
       )
     }
    }
     
    export default ContactInput;
    

    Nice! Component ContactInput sudah selesai. Silakan simpan perubahan kode pada berkas tersebut.
  6. Selanjutnya kita pindah fokus ke component ContactApp menggunakan component ContactInput. Namun sebelum itu, ada baiknya kita siapkan dulu fungsi handler untuk menambahkan data kontaknya. Silakan buka berkas ContactApp.js dan buat method baru bernama onAddContactHandler. Kemudian tuliskan kode untuk mengubah state contacts dengan menambahkan kontak baru berdasarkan data yang dibawa oleh parameter method tersebut.
    import React from 'react';
    import ContactList from './ContactList';
    import { getData } from '../utils/data';
     
    class ContactApp extends React.Component {
     constructor(props) {
       super(props);
       this.state = {
         contacts: getData(),
       }
     
       this.onDeleteHandler = this.onDeleteHandler.bind(this);
       this.onAddContactHandler = this.onAddContactHandler.bind(this);
     }
     
     onDeleteHandler(id) {
       const contacts = this.state.contacts.filter(contact => contact.id !== id);
       this.setState({ contacts });
     }
     
     onAddContactHandler({ name, tag }) {
       this.setState((prevState) => {
         return {
           contacts: [
             ...prevState.contacts,
             {
               id: +new Date(),
               name,
               tag,
               imageUrl: '/images/default.jpg',
             }
           ]
         }
       });
     }
     
     render() {
       return (
         <div className="contact-app">
           <h1>Daftar Kontak</h1>
           <ContactList contacts={this.state.contacts} onDelete={this.onDeleteHandler} />
         </div>
       );
     }
    }
     
    export default ContactApp;
    

    Catatan: Jangan lupa untuk bind nilai this pada method onAddContactHandler di dalam constructor.

    Fungsi onAddContactHandler membawa objek state dari ContactInput di parameter, di mana di dalam objek tersebut hanya ada dua data, yaitu name dan tag. Oleh karena itu, nilai dari properti id dan imageUrl kita berikan secara manual. Properti id diberi dengan nilai timestamp [+new Date()] agar selalu unik, sedangkan nilai dari properti imageUrl kita berikan dengan gambar default.jpg yang tersedia pada folder public/images.

    Kita juga menggunakan spread operator untuk mempertahankan item yang sebelumnya berada di dalam array contacts.

  7. Terakhir, gunakan component ContactInput pada fungsi render() untuk menampilkan form input yang sudah kita buat. Jangan lupa untuk mengisi props addContact dengan nilai this.onAddContactHandler.

    Anda juga bisa sesuaikan struktur HTML-nya menjadi seperti ini.

    import React from 'react';
    import ContactList from './ContactList';
    import { getData } from '../utils/data';
    import ContactInput from './ContactInput';
     
    class ContactApp extends React.Component {
     // constructor dan handler disembunyikan
     
     render() {
       return (
         <div className="contact-app">
           <h1>Aplikasi Kontak</h1>
           <h2>Tambah Kontak</h2>
           <ContactInput addContact={this.onAddContactHandler} />
           <h2>Daftar Kontak</h2>
           <ContactList contacts={this.state.contacts} onDelete={this.onDeleteHandler} />
         </div>
       );
     }
    }
     
    export default ContactApp;
    

  8. Simpan seluruh perubahan kode dan jalankan proyek dengan perintah berikut.

    npm run start
    

    Saat ini aplikasi sudah dilengkapi dengan fitur penambahan kontak. Hasilnya akan terlihat seperti berikut.


Sebelumnya : Event Handling Pada React JS Selanjutnya : Debugging Component menggunakan React DevTools