dewantorafli@gmail.com

Model State Management di Ekosistem React

9 min read

10/12/2023

Konsep state management memiliki peran kunci dalam mengelola data dan state aplikasi dengan efisien. Saat pertama kali mempelajari React.js, banyak orang merasa asing dengan state management, dan sering kali mendengar istilah “React & Redux” sebagai pasangan yang tak terpisahkan.

Pertanyaan mengapa React selalu dikaitkan dengan Redux, jawabannya sederhana: Redux adalah pilihan yang populer dan kuat untuk menangani state management pada aplikasi react. Melalui pemanfaatan Redux, developer dapat dengan mudah mengelola state aplikasi secara terpusat dan mempermudah alur kerja pengembangan. dan sebelum kita masuk ke macam-macam model state management di react, mari kita bahas mengenai state management.

Apa itu state management?

State management merujuk pada cara mengelola dan menyimpan informasi atau data dalam aplikasi. Setiap kali kita bekerja dengan UI yang dinamis, di mana komponen-komponen dapat berinteraksi dan merespon perubahan, kita perlu memiliki cara untuk menyimpan dan memperbarui informasi yang relevan.

Secara sederhana, state management adalah cara aplikasi menyimpan dan mengelola data agar dapat diakses dengan konsisten di seluruh komponen.

State management is nothing more than patterns to make it “easier” for us to change state - crinkles.dev

Dalam aplikasi React, setiap komponen dapat memiliki state sendiri, yang berisi data yang berubah seiring waktu. Namun, ketika aplikasi menjadi lebih kompleks, terutama dengan banyak komponen bersarang atau berbagi data, state management dapat menjadi tantangan.

Misal dalam konteks aplikasi e-commerce, state management menjadi krusial untuk menyediakan pengalaman pengguna yang lancar dan terkoordinasi. Sebagai contoh, pertimbangkan fitur penambahan produk ke keranjang belanja dan alur proses checkout. Pertama-tama, perlu ada suatu cara untuk menyimpan informasi mengenai produk, seperti nama, harga, dan stoknya. Inilah yang disebut sebagai state produk. Setiap kali pengguna menambahkan produk ke dalam keranjang, sistem perlu memantau keranjang belanja dengan detail produk dan kuantitasnya. Oleh karena itu, kita memerlukan state khusus untuk menyimpan informasi keranjang belanja.

Dalam aplikasi ini, proses penambahan produk ke keranjang dilakukan dengan memperbarui state produk dan state keranjang belanja secara bersamaan. Saat pengguna menambahkan suatu produk, informasi produk tersebut ditambahkan ke dalam keranjang belanja, dan stok produk dikurangi untuk mencerminkan pembelian. Dengan demikian, state management memainkan peran kunci dalam menjaga konsistensi dan integritas data di seluruh aplikasi.

Manfaat utama dari pendekatan ini adalah menghindari ketidakselarasan data antar komponen dan memastikan bahwa perubahan satu bagian dari aplikasi secara otomatis tercermin di bagian lainnya. Dengan adanya state management yang baik, kita dapat memberikan pengalaman pengguna yang mulus, di mana informasi keranjang belanja tetap konsisten dan terkini. Ini menciptakan dasar yang kuat untuk mengembangkan fitur-fitur lanjutan seperti proses checkout dengan memanfaatkan state yang terorganisir dengan baik.

Dengan pemahaman tentang pentingnya state management, saatnya kita bahas berbagai model atau jenis state management yang dapat diterapkan dalam pengembangan dengan React.

1. Uni-directional (e.g., Redux, Zustand)

udf Source: toptal

Dalam model uni-directional state management, data mengalir dalam satu arah, umumnya mengikuti arsitektur uni-directional data flow (UDF). Actions meng-trigger perubahan state, dan state diperbarui melalui pure function (reducers).

Karakteristik:

Redux

// actions
const tambah = () => ({ type: "TAMBAH" });
const kurang = () => ({ type: "KURANG" });

// reducer
const counterReducer = (state = 0, action) => {
    switch (action.type) {
        case "TAMBAH":
            return state + 1;
        case "KURANG":
            return state - 1;
        default:
            return state;
    }
};

// store
import { createStore } from "redux";
const store = createStore(counterReducer);

// App
import React from "react";
import { useSelector, useDispatch } from "react-redux";

const App = () => {
    const count = useSelector((state) => state);
    const dispatch = useDispatch();

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => dispatch(tambah())}>Tambah</button>
            <button onClick={() => dispatch(kurang())}>Kurang</button>
        </div>
    );
};

Zustand

import { create } from 'zustand'

type Store = {
  count: number
  inc: () => void
}

const useStore = create<Store>()((set) => ({
  count: 1,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

function Counter() {
  const { count, inc } = useStore()
  return (
    <div>
      <span>{count}</span>
      <button onClick={inc}>one up</button>
    </div>
  )
}

Perbedaan redux & zustand

Secara konseptual, Zustand dan Redux sangat mirip, keduanya didasarkan pada immutable state model. Namun, Redux mengharuskan kita membungkus aplikasi dengan context provider (React Context), zustand tidak.

2. Bi-directional (e.g., MobX, Valtio)

bi-directional Source: toptal

State management bi-direksional memungkinkan aliran data yang lebih fleksibel, di mana perubahan dalam state dapat dipropagasi ke kedua arah. Hal ini sering terkait dengan reactive programming.

Karakteristik:

State management bi-direksional, seperti yang diimplementasikan dalam MobX dan Valtio, memperkenalkan dinamika yang lebih kompleks dalam alur data aplikasi. Pembaruan reaktif memungkinkan perubahan state secara langsung mencerminkan perubahan dalam antarmuka pengguna, dan sebaliknya, menciptakan keterkaitan erat antara model dan tampilan. Observables menjadi inti dari pendekatan ini, memungkinkan pemantauan yang efisien terhadap perubahan dalam state, sehingga respons terhadap perubahan dapat terjadi dengan cepat dan efektif. Pendekatan bi-direksional cocok untuk aplikasi yang membutuhkan interaksi yang dinamis dan cepat antara state dan UI aplikasi.

Mobx

// stores/counter.js
import { makeAutoObservable } from "mobx";

class CounterStore {
  value = 0;

  constructor() {
    makeAutoObservable(this);
  }

  increment() {
    this.value += 1;
  }

  decrement() {
    this.value -= 1;
  }
}

export default CounterStore;

// index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "mobx-react";
import App from "./App";
import CounterStore from "./stores/counter";

ReactDOM.render(
  <Provider counter={new CounterStore()}>
    <App />
  </Provider>,
  document.getElementById("root")
);

// App.js
import React from "react";
import { inject, observer } from "mobx-react";

const App = inject((stores) => ({ counter: stores.counter }))(
  observer(({ counter }) => {
    return (
      <div>
        <div>
          <button onClick={() => counter.increment()}>Increment</button>
          <span>{counter.value}</span>
          <button onClick={() => counter.decrement()}>Decrement</button>
        </div>
      </div>
    );
  })
);

export default App;

3. Atomic (e.g., Jotai, Recoil)

atomic

Dikutip dari dokumentasi jotai,

Build state by combining atoms and renders are automatically optimized based on atom dependency. This solves the extra re-render issue of React context, eliminates the need for memoization, and provides a similar developer experience to signals while maintaining a declarative programming model.

State management atomic melibatkan pemecahan state menjadi bagian-bagian kecil independen yang disebut sebagai atom. Setiap atom dapat dimodifikasi secara independen, dan komponen-komponen dapat subscribe pada atom-atom tertentu untuk mendapatkan value terbaru. Sederhananya state management atomic menggunakan atom sebagai satu sumber state dan kita bisa menggunakannya seperti hook useState pada react namun kita bisa share state/atom tersebut ke komponen lain tanpa melakukan prop drilling. Pendekatan ini menggabungkan pattern state component dan global store seperti redux.

Karakteristik:

Recoil

// atoms.js
import { atom } from "recoil";

export const usernameState = atom({
    key: "usernameState",
    default: "Guest",
});

// App.js
import React from "react";
import { useRecoilState } from "recoil";
import { usernameState } from "./atoms";

const App = () => {
    const [username, setUsername] = useRecoilState(usernameState);

    return (
        <div>
            <p>Welcome, {username}!</p>
            <input
                type='text'
                value={username}
                onChange={(e) => setUsername(e.target.value)}
            />
        </div>
    );
};

Jotai

const priceAtom = createAtom(15);

const discountAtom = createAtom(10);

// derived state
const discountedPriceAtom = createAtom((get) => get(priceAtom) / 100 * get(discountAtom));

const Header = () => {
  const [price, setPrice] = useAtom(priceAtom);
  const [discountedPriceAtom] = useAtom(discountedPriceAtom);
  ...
}

4. Event-driven (e.g., Effector, Storeon, Eventrix)

event-driven

Event-driven state management memperlakukan update state sebagai stream of events. Hal ini sering digunakan dalam skenario di mana perubahan bersifat terus-menerus atau asinkron.

Karakteristik:

Pendekatan state management berbasis event, seperti yang diimplementasikan dalam Effector dan Storeon, melibatkan state sebagai serangkaian event yang berkelanjutan. Stream of events menciptakan model yang sangat responsif terhadap perubahan, memungkinkan pembaruan state untuk terjadi secara asinkron dan efisien. Pendekatan ini berguna dalam skenario di mana perubahan state tidak terbatas pada pembaruan yang terputus-putus, melainkan mengalir secara berkesinambungan/kontinu. Oleh karena itu, event-driven state management cocok untuk aplikasi yang mengandalkan perubahan dinamis dan asinkron yang kontinu, seperti aplikasi real-time atau sistem yang melibatkan banyak interaksi pengguna.

Effector

const fooChange = createEvent();
const fooValue = createStore(4).on(fooChange, (_, e) => +e.currentTarget.value);

const barChange = createEvent();
const barValue = createStore(4).on(barChange, (_, e) => +e.currentTarget.value);

const RangeGroup = ({ name, value, onChange }) => {
    const current = useStore(value);
    return (
        <>
            <label for={name}>{name}</label>
            <input
                name={name}
                type='range'
                min='2'
                max='10'
                step='1'
                value={current}
                onChange={onChange}
            />
            <output for={name}>{current}</output>
        </>
    );
};

const App = () => {
    return (
        <>
            <form>
                <RangeGroup name='foo' onChange={fooChange} value={fooValue} />
                <RangeGroup name='bar' onChange={barChange} value={barValue} />
            </form>
        </>
    );
};

5. Finite State Machine / FSM (e.g., XState)

FSM

Finite State Machine (FSM) merupakan model konseptual yang dapat berada dalam satu dari sejumlah state terbatas pada setiap waktu tertentu. Transisi antar state terjadi sebagai respons terhadap input eksternal, dan perubahan dari state satu ke state yang lain disebut transisi. Sebuah FSM didefinisikan oleh daftar statenya, initial state, dan kondisi-kondisi untuk setiap transisi.

State machines: restricts the possible states something can be in, and limits the possibilities to move from one state to another.

Key characteristic dari FSM melibatkan satu kumpulan state yang terdefinisi dengan baik dalam aplikasi dan transisi antar state yang dipicu oleh event spesifik. Selain itu, FSM dapat mengorganisir state secara hierarki, menyediakan representasi terstruktur dari perilaku aplikasi.

Karakteristik:

XState

// machine.js
import { createMachine } from "xstate";

export const lightSwitchMachine = createMachine({
    id: "lightSwitch",
    initial: "off",
    states: {
        off: { on: { TOGGLE: "on" } },
        on: { on: { TOGGLE: "off" } },
    },
});

// App.js
import React from "react";
import { lightSwitchMachine } from './machines'
import { useMachine } from '@xstate/react'

const App = () => {
    const [state, send] = useMachine(lightSwitchMachine);
    return (
        <div>
            <p>Current State: {state.value}</p>
            <button onClick={() => send({ type: "TOGGLE" })}>Toggle</button>
        </div>
    );
};

Salah satu keunggulan xstate dibanding state management lain yaitu stately (creator dari xstate), membuat vscode extension untuk memvisualisasikan manajemen state dari aplikasi kita, dan kita bisa langsung membuat perubahan state dan event pada visual editor yang mana nantinya kode kita juga akan ikut berubah secara “real-time” dengan perubahan yang kita buat pada visual editor. xstate

Masing-masing model state management ini memiliki kelebihan dan kekurangannya sendiri, dan pilihannya sering kali bergantung pada requirement spesifik dan arsitektur aplikasi. Developer dapat memilih pola berdasarkan faktor-faktor seperti kompleksitas transisi state, kemudahan debugging, dan lain-lain.

Jika kalian ingin melihat perbandingan popularitas dari masing-masing state management yang ada, kalian bisa melihatnya pada link berikut. state-trends

Referensi: