Wednesday

18-06-2025 Vol 19

Mastering `useReducer` in React: A Step-by-Step Guide for Real-World State Management

Menguasai `useReducer` di React: Panduan Langkah Demi Langkah untuk Manajemen State Dunia Nyata

Dalam pengembangan React, mengelola state secara efisien sangat penting untuk membangun aplikasi yang andal dan mudah dipelihara. Sementara `useState` cocok untuk state yang sederhana, `useReducer` muncul sebagai solusi yang kuat untuk mengelola logika state yang lebih kompleks. Posting blog ini akan membimbing Anda melalui seluk-beluk `useReducer`, memberi Anda panduan langkah demi langkah dengan contoh dunia nyata untuk menguasai manajemen state di React.

Daftar Isi

  1. Pendahuluan tentang `useReducer`
  2. Mengapa `useReducer`? Memahami Kebutuhan Akan Alternatif `useState`
  3. Membandingkan `useState` dan `useReducer`: Kapan Menggunakan Apa
  4. Anatomi `useReducer`: Komponen Utama
  5. Reducer Function: Jantung dari `useReducer`
  6. State Awal: Menyiapkan Landasan
  7. Dispatch: Memicu Pembaruan State
  8. Panduan Langkah Demi Langkah untuk Mengimplementasikan `useReducer`
  9. Langkah 1: Mendefinisikan Jenis Tindakan
  10. Langkah 2: Membuat Fungsi Reducer
  11. Langkah 3: Menginisialisasi `useReducer`
  12. Langkah 4: Dispatch Tindakan untuk Memperbarui State
  13. Contoh Dunia Nyata dengan `useReducer`
  14. Contoh 1: Pengelolaan State Formulir Kompleks
  15. Contoh 2: Pengelolaan State Daftar Tugas
  16. Contoh 3: Mengelola State dengan Beberapa Sumber Data
  17. Teknik Tingkat Lanjut dengan `useReducer`
  18. Menggunakan `useContext` dengan `useReducer` untuk Manajemen State Global
  19. Optimasi Kinerja dengan `useMemo` dan `useCallback`
  20. Menangani Efek Samping dalam Reducer
  21. Praktik Terbaik untuk Menggunakan `useReducer`
  22. Menjaga Reducer Tetap Murni
  23. Menulis Tindakan yang Jelas dan Ringkas
  24. Mengelola Kompleksitas dengan Memecah Reducer
  25. Kesalahan Umum yang Harus Dihindari dengan `useReducer`
  26. Mutasi State Secara Langsung
  27. Tidak Menangani Semua Kasus Tindakan
  28. Menggunakan Tindakan yang Terlalu Umum
  29. Alternatif untuk `useReducer`
  30. Library Manajemen State: Redux, Zustand, dan Lainnya
  31. Komponen Kontrol State: Menggunakan Render Props dan Hooks Kustom
  32. Kesimpulan: Memanfaatkan Kekuatan `useReducer`
  33. FAQ (Pertanyaan yang Sering Diajukan)
  34. Apa perbedaan utama antara `useReducer` dan `useState`?
  35. Kapan saya harus menggunakan `useReducer` alih-alih `useState`?
  36. Bagaimana cara menguji komponen yang menggunakan `useReducer`?
  37. Bisakah saya menggunakan `useReducer` dengan TypeScript?
  38. Bagaimana cara memecah reducer yang kompleks menjadi reducer yang lebih kecil?

1. Pendahuluan tentang `useReducer`

`useReducer` adalah hook React yang menyediakan cara alternatif untuk mengelola state di komponen Anda. Ini sangat berguna saat Anda memiliki logika state yang kompleks yang melibatkan beberapa sub-nilai atau saat nilai state berikutnya bergantung pada yang sebelumnya. Pada dasarnya, ini memungkinkan Anda untuk mengelola state menggunakan fungsi reducer dan dispatching tindakan.

2. Mengapa `useReducer`? Memahami Kebutuhan Akan Alternatif `useState`

Meskipun `useState` adalah hook serbaguna untuk mengelola state sederhana, ia mungkin berjuang dengan skenario yang lebih kompleks. Bayangkan mengelola state formulir dengan banyak bidang, atau state aplikasi yang berkembang melalui beberapa transisi. Dalam kasus seperti itu, `useReducer` menawarkan keunggulan berikut:

  • Organisasi yang Lebih Baik: `useReducer` memusatkan logika pembaruan state dalam fungsi reducer, membuat kode Anda lebih terstruktur dan mudah dipahami.
  • Prediktabilitas: Fungsi reducer menerima state saat ini dan tindakan, selalu mengembalikan state baru secara deterministik. Hal ini membuat state Anda lebih dapat diprediksi dan lebih mudah untuk di-debug.
  • Kemudahan Pemeliharaan: Karena logika state terisolasi dalam reducer, ia menjadi lebih mudah untuk dimodifikasi dan dipelihara dari waktu ke waktu.
  • Kemampuan Pengujian: Fungsi reducer adalah fungsi murni, yang berarti mudah diuji secara terpisah tanpa ketergantungan React.

3. Membandingkan `useState` dan `useReducer`: Kapan Menggunakan Apa

Memilih antara `useState` dan `useReducer` tergantung pada kompleksitas kebutuhan manajemen state Anda. Berikut adalah panduan umum:

  • Gunakan `useState` jika:
    • Anda hanya memiliki beberapa nilai state yang sederhana.
    • Logika pembaruan state sederhana dan langsung.
    • Anda tidak memerlukan banyak kontrol atas bagaimana state diperbarui.
  • Gunakan `useReducer` jika:
    • Anda memiliki logika state yang kompleks yang melibatkan beberapa sub-nilai.
    • Nilai state berikutnya bergantung pada yang sebelumnya.
    • Anda ingin memusatkan logika pembaruan state Anda.
    • Anda membutuhkan lebih banyak kontrol atas bagaimana state diperbarui.
    • Anda ingin memudahkan pengujian state Anda.

4. Anatomi `useReducer`: Komponen Utama

`useReducer` melibatkan tiga komponen utama:

  • Fungsi Reducer: Fungsi yang menentukan bagaimana state diperbarui berdasarkan tindakan tertentu.
  • State Awal: Nilai awal state.
  • Dispatch: Fungsi yang digunakan untuk memicu pembaruan state dengan mengirimkan tindakan ke reducer.

5. Reducer Function: Jantung dari `useReducer`

Fungsi reducer adalah jantung dari `useReducer`. Ini adalah fungsi murni yang menerima state saat ini dan tindakan, dan mengembalikan state baru. Bentuk umum dari fungsi reducer adalah sebagai berikut:


    (state, action) => newState
  

Parameter:

  • state: State saat ini.
  • action: Objek yang menjelaskan jenis pembaruan yang perlu dilakukan.

Nilai Kembalian:

  • newState: State yang diperbarui. PENTING: Anda harus mengembalikan state yang *baru*, bukan memodifikasi state yang sudah ada.

Contoh sederhana fungsi reducer:


    const counterReducer = (state, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 };
        case 'DECREMENT':
          return { count: state.count - 1 };
        default:
          return state;
      }
    };
  

Dalam contoh ini, reducer menangani dua jenis tindakan: ‘INCREMENT’ dan ‘DECREMENT’. Berdasarkan jenis tindakan, reducer mengembalikan state baru dengan nilai hitungan yang diperbarui. Perhatikan klausa `default`: selalu mengembalikan state saat ini jika tidak ada tindakan yang cocok.

6. State Awal: Menyiapkan Landasan

State awal adalah nilai awal dari state yang dikelola oleh `useReducer`. Ini adalah nilai yang digunakan saat komponen pertama kali di-mount. State awal dapat berupa tipe data apa pun, seperti angka, string, objek, atau array. Contoh:


    const initialState = { count: 0 };
  

Ini mendefinisikan state awal sebagai objek dengan properti `count` yang diatur ke 0.

7. Dispatch: Memicu Pembaruan State

Fungsi dispatch digunakan untuk memicu pembaruan state. Ini menerima objek tindakan sebagai argumen. Objek tindakan adalah objek biasa yang memiliki properti `type` yang diperlukan dan properti opsional lainnya yang berisi data yang diperlukan untuk memperbarui state. Contoh:


    dispatch({ type: 'INCREMENT' });
  

Ini mengirimkan tindakan dengan jenis ‘INCREMENT’ ke reducer. Reducer kemudian akan menggunakan tindakan ini untuk menghitung state baru dan memperbarui komponen.

8. Panduan Langkah Demi Langkah untuk Mengimplementasikan `useReducer`

Berikut adalah panduan langkah demi langkah untuk mengimplementasikan `useReducer` di komponen React Anda:

Langkah 1: Mendefinisikan Jenis Tindakan

Langkah pertama adalah mendefinisikan jenis tindakan yang akan dikirimkan ke reducer. Jenis tindakan adalah konstanta string yang menjelaskan jenis pembaruan yang perlu dilakukan. Adalah praktik yang baik untuk mendefinisikan jenis tindakan sebagai konstanta untuk menghindari kesalahan ketik dan membuat kode Anda lebih mudah dibaca.


    const INCREMENT = 'INCREMENT';
    const DECREMENT = 'DECREMENT';
  

Langkah 2: Membuat Fungsi Reducer

Selanjutnya, Anda perlu membuat fungsi reducer. Fungsi reducer menerima state saat ini dan tindakan, dan mengembalikan state baru. Pastikan untuk menangani semua jenis tindakan yang mungkin dan mengembalikan state saat ini jika tindakan tidak dikenal.


    const counterReducer = (state, action) => {
      switch (action.type) {
        case INCREMENT:
          return { count: state.count + 1 };
        case DECREMENT:
          return { count: state.count - 1 };
        default:
          return state;
      }
    };
  

Langkah 3: Menginisialisasi `useReducer`

Sekarang Anda dapat menginisialisasi `useReducer` di komponen Anda. `useReducer` menerima dua argumen: fungsi reducer dan state awal. Ia mengembalikan array yang berisi state saat ini dan fungsi dispatch.


    import React, { useReducer } from 'react';

    const initialState = { count: 0 };

    const counterReducer = (state, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 };
        case 'DECREMENT':
          return { count: state.count - 1 };
        default:
          return state;
      }
    };

    function Counter() {
      const [state, dispatch] = useReducer(counterReducer, initialState);

      return (
        <div>
          <p>Count: {state.count}</p>
          <button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
          <button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
        </div>
      );
    }

    export default Counter;
  

Langkah 4: Dispatch Tindakan untuk Memperbarui State

Akhirnya, Anda dapat menggunakan fungsi dispatch untuk mengirimkan tindakan ke reducer dan memperbarui state. Fungsi dispatch menerima objek tindakan sebagai argumen. Objek tindakan harus memiliki properti `type` yang menunjukkan jenis pembaruan yang perlu dilakukan.


    dispatch({ type: INCREMENT });
  

Ini akan mengirimkan tindakan ‘INCREMENT’ ke reducer, yang kemudian akan memperbarui state dengan menaikkan nilai hitungan.

9. Contoh Dunia Nyata dengan `useReducer`

Berikut adalah beberapa contoh dunia nyata tentang bagaimana `useReducer` dapat digunakan untuk mengelola state di aplikasi React:

Contoh 1: Pengelolaan State Formulir Kompleks

`useReducer` sangat berguna untuk mengelola state formulir yang kompleks dengan banyak bidang. Anda dapat menggunakan reducer untuk menangani pembaruan untuk setiap bidang, validasi, dan penanganan pengiriman.


    import React, { useReducer } from 'react';

    const formReducer = (state, action) => {
      switch (action.type) {
        case 'UPDATE_FIELD':
          return { ...state, [action.field]: action.value };
        case 'SUBMIT_FORM':
          return { ...state, submitted: true };
        default:
          return state;
      }
    };

    const initialFormState = {
      name: '',
      email: '',
      message: '',
      submitted: false,
    };

    function ContactForm() {
      const [state, dispatch] = useReducer(formReducer, initialFormState);

      const handleChange = (e) => {
        dispatch({ type: 'UPDATE_FIELD', field: e.target.name, value: e.target.value });
      };

      const handleSubmit = (e) => {
        e.preventDefault();
        dispatch({ type: 'SUBMIT_FORM' });
        // Logika pengiriman formulir Anda di sini
        console.log('Form submitted:', state);
      };

      return (
        <form onSubmit={handleSubmit}>
          <label htmlFor="name">Name:</label>
          <input type="text" id="name" name="name" value={state.name} onChange={handleChange} /><br/><br/>

          <label htmlFor="email">Email:</label>
          <input type="email" id="email" name="email" value={state.email} onChange={handleChange} /><br/><br/>

          <label htmlFor="message">Message:</label>
          <textarea id="message" name="message" value={state.message} onChange={handleChange}></textarea><br/><br/>

          <button type="submit">Submit</button>

          {state.submitted && <p>Form submitted successfully!</p>}
        </form>
      );
    }

    export default ContactForm;
  

Contoh 2: Pengelolaan State Daftar Tugas

`useReducer` juga dapat digunakan untuk mengelola state daftar tugas. Anda dapat menggunakan reducer untuk menangani penambahan, penghapusan, dan penyelesaian tugas.


   import React, { useReducer } from 'react';

    const todosReducer = (state, action) => {
      switch (action.type) {
        case 'ADD_TODO':
          return [...state, { id: Date.now(), text: action.text, completed: false }];
        case 'TOGGLE_TODO':
          return state.map(todo =>
            todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
          );
        case 'DELETE_TODO':
          return state.filter(todo => todo.id !== action.id);
        default:
          return state;
      }
    };

    function TodoList() {
      const [todos, dispatch] = useReducer(todosReducer, []);
      const [newTodoText, setNewTodoText] = React.useState('');

      const handleAddTodo = () => {
        if (newTodoText.trim() !== '') {
          dispatch({ type: 'ADD_TODO', text: newTodoText });
          setNewTodoText('');
        }
      };

      return (
        <div>
          <input
            type="text"
            value={newTodoText}
            onChange={(e) => setNewTodoText(e.target.value)}
            placeholder="Add new todo"
          />
          <button onClick={handleAddTodo}>Add</button>

          <ul>
            {todos.map(todo => (
              <li key={todo.id}>
                <input
                  type="checkbox"
                  checked={todo.completed}
                  onChange={() => dispatch({ type: 'TOGGLE_TODO', id: todo.id })}
                />
                <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
                  {todo.text}
                </span>
                <button onClick={() => dispatch({ type: 'DELETE_TODO', id: todo.id })}>Delete</button>
              </li>
            ))}
          </ul>
        </div>
      );
    }

    export default TodoList;
  

Contoh 3: Mengelola State dengan Beberapa Sumber Data

Jika Anda memiliki state yang bergantung pada beberapa sumber data, `useReducer` dapat membantu Anda mengelola kompleksitasnya. Anda dapat menggunakan reducer untuk menangani pembaruan dari setiap sumber data dan menggabungkannya menjadi satu state yang koheren.

10. Teknik Tingkat Lanjut dengan `useReducer`

Setelah Anda memahami dasar-dasar `useReducer`, Anda dapat menjelajahi teknik yang lebih canggih untuk meningkatkan pengelolaan state Anda.

Menggunakan `useContext` dengan `useReducer` untuk Manajemen State Global

Kombinasi `useReducer` dan `useContext` memungkinkan Anda untuk membuat solusi manajemen state global. Anda dapat membuat konteks yang menyediakan state dan fungsi dispatch ke semua komponen turunan.


    import React, { createContext, useReducer, useContext } from 'react';

    // 1. Membuat Konteks
    const AppContext = createContext();

    // 2. Fungsi Reducer
    const appReducer = (state, action) => {
      switch (action.type) {
        case 'SET_THEME':
          return { ...state, theme: action.payload };
        case 'SET_USER':
          return { ...state, user: action.payload };
        default:
          return state;
      }
    };

    // 3. State Awal
    const initialState = {
      theme: 'light',
      user: null,
    };

    // 4. Provider Konteks Kustom
    const AppProvider = ({ children }) => {
      const [state, dispatch] = useReducer(appReducer, initialState);

      return (
        <AppContext.Provider value={{ state, dispatch }}>
          {children}
        </AppContext.Provider>
      );
    };

    // 5. Hook Kustom untuk Mengakses Konteks
    const useAppContext = () => {
      return useContext(AppContext);
    };

    // Contoh Penggunaan di Komponen Anak
    function ThemeSwitcher() {
      const { state, dispatch } = useAppContext();

      const toggleTheme = () => {
        dispatch({ type: 'SET_THEME', payload: state.theme === 'light' ? 'dark' : 'light' });
      };

      return (
        <button onClick={toggleTheme}>
          Switch to {state.theme === 'light' ? 'dark' : 'light'} theme
        </button>
      );
    }

    // Membungkus Aplikasi dengan Provider
    function App() {
      return (
        <AppProvider>
          <ThemeSwitcher />
          {/* Komponen lain */}
        </AppProvider>
      );
    }

    export default App;
  

Optimasi Kinerja dengan `useMemo` dan `useCallback`

Saat menggunakan `useReducer` dalam komponen yang dioptimalkan, pastikan untuk menggunakan `useMemo` dan `useCallback` untuk menghindari pembuatan ulang yang tidak perlu dari reducer dan fungsi dispatch.


    import React, { useReducer, useCallback } from 'react';

    const initialState = { count: 0 };

    const counterReducer = (state, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return { count: state.count + 1 };
        case 'DECREMENT':
          return { count: state.count - 1 };
        default:
          return state;
      }
    };

    function Counter() {
      const [state, dispatch] = useReducer(counterReducer, initialState);

      // Menggunakan useCallback untuk menghindari pembuatan ulang fungsi dispatch yang tidak perlu
      const increment = useCallback(() => {
        dispatch({ type: 'INCREMENT' });
      }, [dispatch]);

      const decrement = useCallback(() => {
        dispatch({ type: 'DECREMENT' });
      }, [dispatch]);

      return (
        <div>
          <p>Count: {state.count}</p>
          <button onClick={increment}>Increment</button>
          <button onClick={decrement}>Decrement</button>
        </div>
      );
    }

    export default Counter;
  

Menangani Efek Samping dalam Reducer

Meskipun reducer harus berupa fungsi murni, ada kalanya Anda perlu menangani efek samping, seperti panggilan API, dalam reducer. Dalam kasus seperti itu, Anda dapat menggunakan pustaka middleware seperti `redux-thunk` atau `redux-saga` untuk menangani efek samping secara asinkron.

11. Praktik Terbaik untuk Menggunakan `useReducer`

Untuk memastikan Anda menggunakan `useReducer` secara efektif, ikuti praktik terbaik berikut:

  • Menjaga Reducer Tetap Murni: Reducer harus selalu berupa fungsi murni yang mengembalikan state baru berdasarkan state saat ini dan tindakan tanpa efek samping.
  • Menulis Tindakan yang Jelas dan Ringkas: Tindakan harus jelas dan ringkas, menjelaskan pembaruan yang perlu dilakukan.
  • Mengelola Kompleksitas dengan Memecah Reducer: Jika reducer Anda menjadi terlalu kompleks, pecah menjadi reducer yang lebih kecil dan kelola dengan `combineReducers` (jika menggunakan Redux). Untuk `useReducer` murni, Anda dapat menggunakan helper function yang menggabungkan beberapa reducer kecil.

12. Kesalahan Umum yang Harus Dihindari dengan `useReducer`

Hindari kesalahan umum berikut saat menggunakan `useReducer`:

  • Mutasi State Secara Langsung: Selalu buat state baru dan hindari memodifikasi state yang sudah ada.
  • Tidak Menangani Semua Kasus Tindakan: Pastikan untuk menangani semua jenis tindakan yang mungkin dalam reducer Anda.
  • Menggunakan Tindakan yang Terlalu Umum: Hindari menggunakan tindakan yang terlalu umum yang dapat menyebabkan perilaku yang tidak terduga.

13. Alternatif untuk `useReducer`

Meskipun `useReducer` adalah alat yang ampuh, ada alternatif lain untuk manajemen state di React:

  • Library Manajemen State: Redux, Zustand, dan lainnya menawarkan solusi yang lebih komprehensif untuk mengelola state global dalam aplikasi yang besar.
  • Komponen Kontrol State: Menggunakan Render Props dan Hooks Kustom dapat menjadi cara yang baik untuk mengelola state yang lebih kecil dan lebih terisolasi.

14. Kesimpulan: Memanfaatkan Kekuatan `useReducer`

`useReducer` adalah hook React yang kuat yang memungkinkan Anda mengelola state yang kompleks dengan cara yang terstruktur dan terprediksi. Dengan memahami komponen utama, mengikuti praktik terbaik, dan menghindari kesalahan umum, Anda dapat memanfaatkan kekuatan `useReducer` untuk membangun aplikasi React yang andal dan mudah dipelihara.

15. FAQ (Pertanyaan yang Sering Diajukan)

  • Apa perbedaan utama antara `useReducer` dan `useState`?

    `useState` cocok untuk state sederhana dengan pembaruan langsung, sedangkan `useReducer` unggul untuk state yang kompleks dengan logika pembaruan yang terlibat.

  • Kapan saya harus menggunakan `useReducer` alih-alih `useState`?

    Gunakan `useReducer` ketika Anda memiliki logika state yang kompleks, state bergantung pada yang sebelumnya, atau Anda ingin memusatkan logika pembaruan state Anda.

  • Bagaimana cara menguji komponen yang menggunakan `useReducer`?

    Anda dapat menguji fungsi reducer secara terpisah karena ini adalah fungsi murni. Untuk pengujian komponen, Anda dapat menggunakan pustaka seperti React Testing Library untuk berinteraksi dengan komponen dan memastikan state diperbarui seperti yang diharapkan.

  • Bisakah saya menggunakan `useReducer` dengan TypeScript?

    Ya, `useReducer` sangat cocok dengan TypeScript. Anda dapat menentukan tipe untuk state, tindakan, dan reducer untuk memastikan keamanan tipe.

  • Bagaimana cara memecah reducer yang kompleks menjadi reducer yang lebih kecil?

    Anda dapat membuat beberapa reducer yang lebih kecil, masing-masing mengelola bagian state tertentu. Kemudian, Anda dapat menggunakan fungsi helper untuk menggabungkan reducer ini ke dalam satu reducer utama.

“`

omcoding

Leave a Reply

Your email address will not be published. Required fields are marked *