Multiple Error Handling di Redux


Photo by Jase Ess / Unsplash

Halo, kali ini saya akan mencoba membuat tutorial singkat tentang error handling di aplikasi yang berbasis react-redux. Error Handling sangat penting dalam aplikasi terutama aplikasi yang langsung berhubungan dengan user. bahkan beberapa programmer hebat menyarankan kita untuk berpikir error first, alias selalu berpikir bahwa fungsi atau module yang kita buat akan error.

nah sebenarnya mudah saja membuat error handling pada reducer. tinggal tambahkan field error pada reducer dan selesai kan ? dibawah ini contoh reducer yang “siap error”


const initialState = Map({
  is_loading: false,
  is_finish: null,
  error: {
    code: '',
    message: '',
  },
});

const formReducer = (state = initialState, action) => {
  switch (action.type) {
    case FORM.START_POST:
      return state.withMutations(state =>
        state.set('is_loading', true)
              .set('error', Map())
              .set('is_finish', false)
      );
    case FORM.SUCCESS_POST:
      setTimeout(() => state.set('is_finish', false), 2500);
      return state.withMutations(state =>
        state.set('is_loading', false)
              .set('error', Map())
              .set('is_finish', true)
      );
    case FORM.FAILED_POST:
      setTimeout(() => state.set('is_error', false), 2500);
      setTimeout(() => state.set('is_finish', false), 2500);
      return state.withMutations(state =>
        state.set('is_loading', false)
              .set('error', fromJS({action.message, action.code})
              .set('is_finish', true)
      );
    default:
      return state;
  }
};

dan tinggal di pake di fungsi render, mau model ngarahin ke error page ataupun pake popup, itu soal selera saja.

//
// .... sisa aplikasi

//fungsi render
render() {
  const { formReducer } = this.props;
  // cek kalau field error ada isinya
  const isError = formReducer.get('error').size > 0
  
  // kalau mau render full page error atau redirect tinggal :
  if (isError) {
    return (<ErrorPage ...props />)
  }
  
  // atau kalau mau mengeluarkan popup daripada pake error page bisa 
  return (
   <View>
    //jika is Error true, render popup Error
    {isError &&
     <ErrorPopup ...props />  
    }
    <PageComponent />
    </View>
  )
}

//jangan lupa pake mapStateToProps dan connect
const mapStateToProps = state => ({
  formReducer: state.formReducer
});

connect(mapStateToProps)(YourComponent)

Oh iya, di contoh di atas saya pakai immutable.js, utility untuk struktur data immutable dari facebook. utility ini sangat berguna untuk functional programming dan juga sangat berguna untuk aplikasi react redux. karena dua module tersebut sebenarnya memaksa kita untuk lebih memilih “membuat data” dari pada “mengubah data”. terlebih lagi immutable memiliki bebrapa fungsi tambahan untuk mengambil data dari struktur datanya yang memudahkan kita kalau memiliki store redux yang complex

well, sejauh ini kita sudah punya component yang punya error handling. we happy, tapi masalah sebenarnya, dalam applikasi yang lebih kompleks bisa lebih dari satu component yang mengeluarkan error dan bukan hanya form seperti diatas. kalau hanya sedikit lebih kompleks (katakanlah 2–3 reducer) mungkin masih reasonable kalau kita ulangi aja step diatas untuk setiap reducer. tapi ketika jumlah reducer sudah lebih banyak dan hubungan antar komponen makin kompleks, misalkan, kita punya fitur notifikasi, dan notifikasi bisa muncul dimanapun yang juga berati bisa error di manapun. bagaiman cara mengimplementasikan error handling nya?

Salah satu cara yang simple ketika tidak ingin refactor codingan reducer adalah dengan mengumpulkan nya di root component. dan di mapping, bisa melakukan mapping melalui fungsi render, ataupun melalui mapStateToProps. sebenarnya ketika kita hanya membutuhkan beberapa reducer hanya field error nya saja, cara kedua lebih ideal. karena component kita gak mendapat props-props yang gak dibutuhkan. berikut contohnya

// sisa dari aplikasi kita

//fungsi render
render() {
 //alternative 1, mapping di render
 const { formReducer, loginReducer, postReducer, errorList } = this.props;
 //bisa di masukin array dulu sih kalau mau, tapi saya prefer ini
 //karena masih reasonable untuk di stack di if
 if (formReducer.get('error') ||
     loginReducer.get('error') ||
     postReducer.get('error')) {
   //seperti awal, alternative menampilkan error page
      return <ErrorPage ..props />
    }
 //alternative 2, mapping di mapToState (lihat di bawah)
 return (
   <View>
     // cek dulu kalau size nya 0 abaikan block ini
     { errorList.length > 0 && errorList.map( errorObj =>
          <ErrorPopup type={errorObj.type} message={errorObj.message} />
       )
     }
    <MainComponent />
   </View>
 );
}

// gunakan map state to props untuk mapping error
const mapStateToProps = state => ({
    errorList: [state.formReducer.get('error'), state.postReducer.get('error'), state.loginReducer.get('error')]
    // .. other mapping
});
connect(mapStateToProps)(YourComponent)

well done. sekarang kita punya error handling di root component yang akan mendeteksi erorr di child component. well tetapi terlalu banyak repetisi yang kita lakukan, misalnya harus membuat field error pada tiap reducer. 3 recuder masih reasonable, tapi apakah scalable? dan lagipula cara diatas sangat rentan bug. karena dimana ada code yang diulang disitu rawan muncul bug. let’s go DRY.

nah the best way untuk handling multiple error sebenernya saya temukan di source code contoh di redux (Real World Example):

Redux Real world
cek kode yang saya highlight, sekilas itu tidak tampak seperti reducer. ya, tapi itu adalah reducer. tapi dia tidak nge switch dari type seperti yang biasa kita lakukan. tapi dia melakukan kondisional berdasarkan payload. jadi secara simpel sebenarnya setiap action yang kita dispatch itu melakukan cek terhadap seluruh store. yap, seperti reducer error di real-world example nya redux di bawah

const errorMessage = (state = null, action) => {
  const { type, error } = action

  if (type === ActionTypes.RESET_ERROR_MESSAGE) {
    return null
  } else if (error) {
    return error
  }

  return state
}

yeah yeah, redux bisa ngeswitch berdasarkan payload karena sebenarnya inti dari reducer adalah dimasukin state, harus keluar state baru. terserah gimana caranya. dan diatas caranya adalah dengan memfilter payload. dengan begini kita tidak perlu memiliki action yang berhubungan dengan error !

yap, ketika kita punya error reducer seperti yang tadi, dan fungsi failedFetch seperti diatas yang terjadi ketika kita mendispatch fungsi failedFetch adalah, fetchReducer akan tetap memproses action.type FETCH.FAILED .. tapi di sisi lain, reducer error kita juga akan mendeteksi error karena di action yang kita dispatch ada field errornya.. ain’t that cool ?

sekian dulu tulisan dari saya, semoga bermanfaat.