Reducer dengan Side Effects. Emang Bisa?

Dibisain aja lah!

Jihad Dzikri Waspada
codewey
4 min readMay 18, 2019

--

Launch rocket as a side-effect (source)

Temen-temen di sini pasti sudah paham lah ya konsep reducer: sebuah pure function untuk mengolah state atau sebagian state aplikasi kita. Saya pribadi sangat menyukai konsep reducer ini karena komputasi state aplikasi kita dilakukan di satu tempat saja. Pun sifatnya yang pure, tidak ada side-effects (i.e HTTP request, logging, IO, dll), agar menjamin state kita tetap predictable dan deterministic. Apalagi beberapa saat yang lalu React meluncurkan fitur React Hooks, dimana salah satu diantaranya adalah useReducer yang memungkinkan penggunaan reducer di scope component saja.

Nah karena sifat reducer yang pure, kita “harus” menggunakan beberapa library tambahan untuk melakukan side-effects seperti redux-thunk, redux-saga, redux-observable, dll. Tapi jujur aja, selama kurang lebih 3 taun saya makek konsep reducer ini, gak jarang juga saya ngedumel dan berharap bisa melakukan side-effects langsung di dalam reducer. Biar gampang aja gitu. Soalnya kadang-kadang agak males dengan ceremony-nya redux: musti bikin action creator, reducer, mapStateToProps, mapDispatchToProps, dll 🤯

Ditambah lagi kalo mau make hooks useReducer yang memang mainannya di level component, sedangkan library-library yang disebutkan tadi biasanya main di level global. Terus kalo effect yang mau dijalanin itu receh, ngelakuin ceremony redux berasa overkill dan berdosa aja gitu.

Pertanyaannya: ada cara yang simple gak sih?

📖 Case Study

Yang gampang aja ya case study-nya: kita mau buat aplikasi counter biasa, yang bisa increment ama decrement gitu. Tapi dengan syarat, nilai counter-nya gak boleh dibawah 0. Jika tetep di-decrement, maka kita mau ngelakuin side-effect berupa tampilan alert dengan message: “GAK BOLEH DI BAWAH 0 BGSD!” Ups maap ngegas 🤭

Untuk increment sama decrement-nya pasti gampang lah ya, udah banyak contohnya jugak:

All is well. Sekarang tinggal kita handle side-effect-nya deh di component. React Hooks menyediakan fungsi useEffect untuk melakukan, ummm, side-effects di component.

⚠️ Problem is..

Masalah-nya pada pendekatan di atas adalah state kita leak. “Bocor bocor bocor!” Kata salah satu tokoh besar di negeri kita. Leak-nya adalah ketika kita membiarkan counter bernilai -1 dan komponen mengetahui-nya terus buru-buru “mengkoreksi” dengan dispatch(‘INCREMENT’). Not good at all.

Pendekatan yang benar adalah kita harus meng-en-kap-su-la-si state kita di dalam reducer itu sendiri sehingga state yang keluar dari reducer itu hanyalah state yang valid saja. Kita harus mencegah invalid state. Invalidate the invalids. Kayak di salah satu artikel saya ini 👇🏻

Jadi solusinya? Kita ubah sedikit reducernya menjadi

Dengan begini counter kita gak akan pernah bisa di bawah 0. Udah gak bocor lagi 😉.

Terus component-nya?

Dhuaar! Bingung dah lu. Gimaana caranya kita nge-detect user lagi mencet tombol DECREMENT sedangkan state kita 0? Kita cuman punya dispatch(‘DECREMENT’) yang dikirim dari component ke reducer. Andaikata aja action ini bisa ditangkep di dalam useEffect macam

mungkin bakalan lebih gampang 🤔.

Atau mungkin kalo dipikir-pikir, kita bisa aja bikin work-around dan gak make useEffect:

Cuman ya gitu, masak di component kita musti cek logic lagi if (state === 0) .. sedangkan logic yang sama baru aja kita implementasikan di reducer. Solusi ini pun berpotensi terjadinya state leak juga. Duplicated logic. Smelly code.

Mungkin yang kita butuhkan adalah: “sesuatu” yang dikirim dari reducer ke component sehingga component tau kapan harus melakukan side-effect.

🌟 Solution

Entah ini beneran solusi atau another work-around, tapi cara ini cukup efektif dan simple menurut saya pribadi: kita ubah “state” di reducer menjadi “state dan effect”.

Maksudnya? Kita akan pake struktur data Tuple yang berisi state dan effect. Di Javascript, tuple ini bisa direpresentasikan dengan array [state, effect]. Kemudian dengan menggunakan teknik array destructuring, reducer kita kurang lebih akan berubah menjadi

Dan di component pun bisa dengan mudah mendeteksi perubahan state dan sinyal untuk melakukan side-effect.

Dengan pendekatan seperti ini, reducer kita bisa menkontrol baik state maupun effect sekaligus. Dengan catatan effect yang dihasilkan dari reducer ini tetap bersifat pure: ia hanya berupa deskripsi side-effect, mirip-mirip kayak action creator. Kalau action creator itu dikirim dari component ke reducer, effect yang ini dikirim dari reducer ke component. Dan yang paling penting: state kita gak bocor ke component! Yeaa 🎉

Singkat kata, type signature reducer yang awalnya (state, actions) => state sedikit berubah menjadi ([state, effect], action) => [state, effect]. In fact, filosofi side-effect sebagai deskripsi/messages ini ternyata menjadi ide utama dari library redux-loop. Sebagaimana dinyatakan di dokumentasinya:

The values returned from the reducer when scheduling an effect with redux-loop only “describe” the effect

Kalau mau contoh yang agak real world, saya sudah siapkan repo yang simple:

beserta “effectful reducer”-nya dimari.

If you find this article useful, don’t hesitate to hit the clap button so that your friends could know this story.

Semoga bermanfaat 😉

--

--

Jihad Dzikri Waspada
codewey

Software Developer @Chordify, Utrecht. NOTE: Please navigate to https://jihadwaspada.com. I no longer write on Medium