{"id":199,"date":"2020-04-05T10:11:35","date_gmt":"2020-04-05T10:11:35","guid":{"rendered":"https:\/\/sentraldigitalindonesia.co.id\/blog\/?p=199"},"modified":"2020-04-05T10:11:40","modified_gmt":"2020-04-05T10:11:40","slug":"apa-itu-redux","status":"publish","type":"post","link":"https:\/\/sentraldigitalindonesia.co.id\/blog\/2020\/04\/05\/apa-itu-redux\/","title":{"rendered":"Apa Itu Redux ?"},"content":{"rendered":"\n<p>Redux merupakan&nbsp;<em>back-end<\/em>&nbsp;yang berkolaborasi dengan&nbsp;<strong><em>react native<\/em><\/strong><em>.&nbsp;<\/em>Belajar&nbsp;<em>framework<\/em>&nbsp;baru merupakan hal yang menantang dan mmanaenyusahkan, namun sekali menemukan alur pengkodean (menulis code) akan lebih mudah untuk mengimplementasikan.<\/p>\n\n\n\n<p>Menjadi seorang pemula dalam Redux membuat saya membaca banyak referensi hal dasar yang harus ada dalam aplikasi berbasis Redux. Setelah membaca lebih dari ratusan kata (lebay, dalam artian lain beberapa artikel), saya menemukan basis dari aplikasi Redux. Basis dalam hal ini yaitu bagian dasar yang bisa membuat aplikasi Redux dapat berjalan.<\/p>\n\n\n\n<p>Implementasi Redux bisa digambarin pake diagram berikut:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/i0.wp.com\/masputih.com\/wordpress\/wp-content\/uploads\/2017\/12\/redux-diagram-580x331.jpg?resize=580%2C331\" alt=\"\" class=\"wp-image-9131\"\/><\/figure><\/div>\n\n\n\n<p>Jadi ada 4 aktor utama di dalam sistem Redux:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Action<\/li><li>Store<\/li><li>Reducer<\/li><li>State<\/li><\/ol>\n\n\n\n<p>App\/UI membuat objek&nbsp;<strong>Action<\/strong>&nbsp;dan mengirimkannya ke&nbsp;<strong>Store<\/strong>. Oleh&nbsp;<strong>Store<\/strong>, objek&nbsp;<strong>Action<\/strong>&nbsp;diteruskan ke&nbsp;<strong>Reducer<\/strong>&nbsp;yang bertugas melakukan&nbsp;<em>update<\/em>&nbsp;terhadap&nbsp;<em>State<\/em>. Kalo ada pembaruan&nbsp;<strong>State<\/strong>&nbsp;maka&nbsp;<strong>Store<\/strong>&nbsp;mengirim objek&nbsp;<strong>State<\/strong>&nbsp;yang baru ke semua bagian App yang jadi&nbsp;<strong>subscriber<\/strong>&nbsp;\/&nbsp;<strong>listener<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Action<\/h3>\n\n\n\n<p>Action adalah objek sederhana yang wajib punya properti bernama&nbsp;<code>type<\/code>&nbsp;&amp; bertipe&nbsp;<code>string<\/code>. Action boleh berisi data lain yang mungkin diperlukan untuk update state, tapi yang paling pokok adalah&nbsp;<code>type<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const add = {   type:'ADD',   value: 100 } const start = {   type: 'START' }<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Reducer<\/h3>\n\n\n\n<p>Reducer adalah sebuah&nbsp;<em>function<\/em>&nbsp;yang bertugas memproses&nbsp;<strong>Action<\/strong>&nbsp;dan bikin&nbsp;<strong>State<\/strong>&nbsp;baru. Reducer punya dua parameter&nbsp;<code>state<\/code>&nbsp;&amp;&nbsp;<code>action<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const reducer = ( state = {}, action) =>{   if(action.type === ....) {     \/\/ blah blah     return newstate;   } else if(action.type === ....) {     \/\/ blah blah blah     return otherstate   }   return state; };<\/code><\/pre>\n\n\n\n<p>Syarat Reducer adalah dia harus berupa&nbsp;<em>pure function<\/em>. Apa itu ?<\/p>\n\n\n\n<p><em>Pure Function<\/em>&nbsp;(PF) adalah&nbsp;<em>function<\/em>&nbsp;yang:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Selalu memberi nilai balik yang sama selama argumennya sama.<\/li><li>Nggak mengubah (<em>mutate<\/em>) objek atau variabel lain<\/li><li>Nggak tergantung\/terpengaruh objek atau variabel lain<\/li><\/ol>\n\n\n\n<p>Contoh yang bukan PF:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>let ammo = 100; const shoot = () =>{   \/\/ngerubah variabel di luar fn   return ammo--; } const canShoot = ( round ) =>{   \/\/tergantung variabel di luar fn   if( round >= ammo){     return true;   }   return false }<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Store<\/h3>\n\n\n\n<p><em>Store<\/em>&nbsp;adalah objek yang menghubungkan&nbsp;<em>Action<\/em>&nbsp;&amp;&nbsp;<em>Reducer<\/em>. Pada intinya, objek ini bertugas:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Menyimpan&nbsp;<em>State<\/em><\/li><li>Menyediakan API untuk mengakses&nbsp;<em>State<\/em><\/li><li>Menyediakan API untuk&nbsp;<em>update<\/em>&nbsp;<em>State<\/em><\/li><li>Menyediakan API biar objek lain bisa jadi&nbsp;<em>listener<\/em>&nbsp;\/&nbsp;<em>subscriber<\/em><\/li><li>Menyediakan API untuk melepas&nbsp;<em>listener<\/em>&nbsp;\/&nbsp;<em>subscriber<\/em>.<\/li><\/ol>\n\n\n\n<p>Satu aplikasi hanya boleh punya satu&nbsp;<em>Store<\/em>. Tapi satu&nbsp;<em>Store<\/em>&nbsp;bisa punya banyak&nbsp;<em>Reducer<\/em>. Jadi kalo kita ingin memecah kode yang bertugas menghandel data ke dalam beberapa modul, kita bisa bikin beberapa&nbsp;<em>Reducer<\/em>&nbsp;&amp; nanti digabung dalam satu&nbsp;<em>Store<\/em>. Misalnya kayak di bawah ini:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/i2.wp.com\/masputih.com\/wordpress\/wp-content\/uploads\/2017\/12\/multi-reducers.jpg?resize=532%2C215\" alt=\"\" class=\"wp-image-9137\"\/><\/figure><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Contoh Aplikasi: Video Player<\/h2>\n\n\n\n<p>Redux didistribusikan sebagai modul NPM. Jadi kita instal dulu:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>$ yarn init -y &amp;&amp; yarn add redux -E -S<\/code><\/pre>\n\n\n\n<p>Selain Redux, kita pake&nbsp;<a href=\"http:\/\/masputih.com\/2017\/12\/bikin-project-javascript-modern-dengan-poi\">Poi<\/a>&nbsp;untuk transpile ES6 &amp; jalanin server lokal.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Template HTML<\/h3>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>&lt;!-- file: index.ejs --> &lt;h1>My Video Player&lt;\/h1> &lt;div id=\"app\">   &lt;video id=\"myvideo\">&lt;\/video>   &lt;div id=\"controlbar\">     &lt;div>       &lt;input type=\"text\" name=\"video-source\" value=\"\" placeholder=\"video url\">       &lt;button id=\"load-src-btn\">Load&lt;\/button>     &lt;\/div>     &lt;div>       &lt;button id=\"play-pause-btn\">Play&lt;\/button>       &lt;button id=\"vol-up-btn\">Vol+&lt;\/button>       &lt;button id=\"vol-down-btn\">Vol-&lt;\/button>       &lt;div id=\"time\">         &lt;span>Time: &lt;\/span>         &lt;input type=\"text\" name=\"time\" value=\"0\">         &lt;span>\/&lt;\/span>         &lt;span id=\"duration\">00&lt;\/span>       &lt;\/div>     &lt;\/div>   &lt;\/div> &lt;\/div><\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Stylesheet<\/h4>\n\n\n\n<p>Sebenernya nggak penting sih. Tapi ya biar app-nya nggak jelek-jelek amat.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>*{   margin:0;padding:0; } body{   font-size: 1.2em;   line-height: 1.5em;   padding:10px; } h1{   margin: 1em 0; } video{   width:640px;   height:360px;   position: relative;   background: #66CCFF; } #controlbar{   line-height: 2em;   font-size:14px;   input[type=text]{     width:90%;     max-width:640px;     padding: 5px;     font-size:100%;   }   input[name=time]{     width:4em;   }   button{     font-size:0.8em;     padding:5px;   }   :last-child{     margin-top:10px;   }   #time{     display:inline-block;     text-align: center;   } }<\/code><\/pre>\n\n\n\n<p>Terus bikin file&nbsp;<em>entry<\/em>&nbsp;buat appnya sendiri:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/index.js import '.\/styles.scss'<\/code><\/pre>\n\n\n\n<p>Nyalain Poi:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>$ poi src\/index.js<\/code><\/pre>\n\n\n\n<p>Terus buka&nbsp;<code>localhost:4000<\/code>.<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/i1.wp.com\/masputih.com\/wordpress\/wp-content\/uploads\/2017\/12\/redux-video-01-580x536.jpg?resize=580%2C536\" alt=\"\" class=\"wp-image-9139\"\/><\/figure><\/div>\n\n\n\n<p>Lanjut ke \u201ckodingannya\u201d ( bahasa programmer jaman NOW ).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">State, Reducer &amp; Store Minimalistik<\/h3>\n\n\n\n<p>Pertama kita bikin object&nbsp;<em>State<\/em>-nya. Sederhana aja,<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/index.js import '.\/styles.scss' const defaultVideoState = {   source: undefined,   status: undefined,   volume: 1,   duration: 0,   time: 0 };<\/code><\/pre>\n\n\n\n<p>Lanjut bikin&nbsp;<em>Reducer<\/em>, masih di file&nbsp;<code>src\/index.js<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>const videoStateReducer = (state = defaultVideoState, action) => {   return state; };<\/code><\/pre>\n\n\n\n<p>Terus bikin&nbsp;<em>Store<\/em>&nbsp;&amp; subscriber:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/import dulu import { createStore } from 'redux'; const videoStore = createStore( videoStateReducer ); \/\/ekspos ke global NS biar bisa dipanggil di konsol window.videoStore = videoStore; \/\/tambah subscriber buat ngetes videoStore.subscribe(()=>{   console.log('VideoStore:current state', videoStore.getState()); })<\/code><\/pre>\n\n\n\n<p>Sekarang kita tes dulu koneksi antara&nbsp;<em>Store<\/em>,&nbsp;<em>Reducer<\/em>&nbsp;&amp; subscribernya. Buka konsol &amp; coba kirim&nbsp;<em>Action<\/em>&nbsp;ke&nbsp;<code>videoStore<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>window.videoStore.dispatch({ type:'HELLO' });<\/code><\/pre>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/i2.wp.com\/masputih.com\/wordpress\/wp-content\/uploads\/2017\/12\/store-test-580x316.gif?resize=580%2C316\" alt=\"\" class=\"wp-image-9140\"\/><\/figure><\/div>\n\n\n\n<p>Jadi dari hasil testing di atas, bisa diliat prosesnya:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li><code>videoStore<\/code>&nbsp;menerima&nbsp;<em>Action<\/em>&nbsp;berupa objek&nbsp;<code>{ type: 'HELLO'}<\/code><\/li><li><em>Action<\/em>&nbsp;diterusin ke&nbsp;<code>videoStateReducer<\/code><\/li><li><code>videoStore<\/code>&nbsp;jalanin listener\/subscriber<\/li><\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Action<\/h3>\n\n\n\n<p>Video player ini butuh 6 action (kemungkinan nanti nambah):<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>SET_SOURCE<\/li><li>PLAY<\/li><li>PAUSE<\/li><li>SEEK<\/li><li>VOLUME_UP<\/li><li>VOLUME_DOWN<\/li><\/ol>\n\n\n\n<p>Kalo setiap kali butuh Action kita harus bikin objek secara manual pasti repot &amp; resiko error ( typo dsb). Jadi kita bikin yang namanya&nbsp;<em>Action Generator<\/em>\/&nbsp;<em>Action Factory<\/em>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/actions.js \/\/konstanta buat Action type biar nggak salah ketik export const SET_SOURCE = 'SET_SOURCE'; export const PLAY = 'PLAY'; export const PAUSE = 'PAUSE' export const SEEK = 'SEEK'; export const VOLUME_UP = 'VOLUME_UP'; export const VOLUME_DOWN = 'VOLUME_DOWN'; export const setSource = (url) =>({   type:SET_SOURCE,   url }); export const play = () =>({   type:PLAY }); export const pause = () =>({   type:PAUSE }); export const seek = (time) =>({   type:SEEK,   time }); export const volumeUp = () =>({   type:VOLUME_UP }); export const volumeDown = () =>({   type:VOLUME_DOWN });<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Set-Source<\/h3>\n\n\n\n<p>Di file&nbsp;<code>src\/index.js<\/code>, kita update&nbsp;<code>videoStateReducer<\/code>&nbsp;untuk handel action&nbsp;<code>SET_SOURCE<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/index.js \/\/import dulu semuanya import * as Actions from '.\/actions'; const videoStateReducer = (state = defaultVideoState, action) => {   console.log('VideoStateReducer','state',state,'action', action);   switch(action.type){     case Actions.SET_SOURCE:       return {         ...state,         source: action.url       }   }   return state; };<\/code><\/pre>\n\n\n\n<p>Di baris paling bawah sendiri, setelah&nbsp;<code>subscribe<\/code>, kita coba dispatch action&nbsp;<code>SET_SOURCE<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>videoStore.subscribe((state)=>{   console.log('VideoStore:current state', videoStore.getState()); }); \/\/DISPATCH ACTION videoStore.dispatch(Actions.setSource('http:\/\/google.com'));<\/code><\/pre>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><a href=\"https:\/\/i0.wp.com\/masputih.com\/wordpress\/wp-content\/uploads\/2017\/12\/set-source-diagram.jpg\"><img decoding=\"async\" src=\"https:\/\/i0.wp.com\/masputih.com\/wordpress\/wp-content\/uploads\/2017\/12\/set-source-diagram-580x218.jpg?resize=580%2C218\" alt=\"\" class=\"wp-image-9141\"\/><\/a><\/figure><\/div>\n\n\n\n<p>Berikutnya, bikin file&nbsp;<code>src\/video_wrapper.js<\/code>&nbsp;untuk logic playernya :<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/video_wrapper.js const VideoWrapper = (store) => {   const videoEl = document.getElementById('myvideo');   \/\/subscribe   store.subscribe(( )=>{     let state = store.getState();     if(state.status === 'new_source'){       videoEl.src = state.source;     }   }); } export default VideoWrapper;<\/code><\/pre>\n\n\n\n<p>Dan edit dikit&nbsp;<em>Reducer<\/em>-nya, tambahin&nbsp;<code>status:'new_source'<\/code><\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/index.js const videoStateReducer = (state = defaultVideoState, action) => {   console.log('VideoStateReducer','state',state,'action', action);   switch(action.type){     case Actions.SET_SOURCE:       return {         ...state,         status:'new_source', \/\/BARIS BARU         source: action.url       }   }   return state; };<\/code><\/pre>\n\n\n\n<p>Inisialisasi&nbsp;<code>VideoWrapper<\/code>&nbsp;&amp; coba dispatch action pake URL video:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>import VideoWrapper from '.\/video_wrapper'; \/\/dst \/\/init videowrapper VideoWrapper(videoStore); \/\/coba videoStore.dispatch(Actions.setSource('\/\/vjs.zencdn.net\/v\/oceans.mp4'));<\/code><\/pre>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/i2.wp.com\/masputih.com\/wordpress\/wp-content\/uploads\/2017\/12\/set-source-video.jpg?resize=489%2C175\" alt=\"\" class=\"wp-image-9142\"\/><\/figure><\/div>\n\n\n\n<p>Karena statusnya nanti variatif, biar lebih aman kita bikin konstanta buat&nbsp;<code>status<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: status.js export const NEW_SOURCE = 'new_source'; export const PLAY_REQUESTED = 'play_requested'; export const PLAYING = 'playing'; export const PAUSE_REQUESTED = 'pause_requested'; export const PAUSED = 'paused';<\/code><\/pre>\n\n\n\n<p>Update&nbsp;<code>src\/index.js<\/code>&nbsp;&amp;&nbsp;<code>src\/video_wrapper.js<\/code>, pake konstan di atas:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/index.js import * as Status from '.\/status'; \/\/dsb \/\/di videoStateReducer(): switch(action.type){   case Actions.SET_SOURCE:     return {       ...state,       status: Status.NEW_SOURCE, \/\/pake konstanta       source: action.url     } } \/\/dsb<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/video_wrapper.js import * as Status from '.\/status'; \/\/dsb   store.subscribe(( )=>{     const state = store.getState();     if(state.status === Status.NEW_SOURCE){       videoEl.src = state.source;     }   }); \/\/dsb<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Input UI<\/h4>\n\n\n\n<p>Bikin file&nbsp;<code>src\/ui\/source-input.js<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/ui\/source-input.js import { setSource } from '..\/actions'; const SourceInput = (store)=>{   const input = document.querySelector('input[name=video-source]');   \/\/default video   input.setAttribute('value','\/\/vjs.zencdn.net\/v\/oceans.mp4');   const btn = document.getElementById('load-src-btn');   btn.addEventListener('click', (e)=>{     e.preventDefault();     \/\/kalo input nggak kosong,     \/\/dispatch action SET_SOURCE     if(input.value){       store.dispatch(setSource(input.value));     }   }) } export default SourceInput;<\/code><\/pre>\n\n\n\n<p>Balik ke&nbsp;<code>src\/index.js<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/index.js \/\/import dulu import SourceInput from '.\/ui\/source-input'; \/\/dsb \/\/scroll ke bawah ... \/\/init videowrapper VideoWrapper(videoStore); \/\/init SourceInput SourceInput(videoStore); \/\/komen atau hapus baris ini \/\/videoStore.dispatch(Actions.setSource('\/\/vjs.zencdn.net\/v\/oceans.mp4'));<\/code><\/pre>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter\"><img decoding=\"async\" src=\"https:\/\/i2.wp.com\/masputih.com\/wordpress\/wp-content\/uploads\/2017\/12\/src-input-ui.gif?resize=516%2C810\" alt=\"\" class=\"wp-image-9145\"\/><\/figure><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Play &amp; Pause<\/h3>\n\n\n\n<p>Sekarang bikin logic utk play &amp; pause video. Mulai dari reducer.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/index.js const videoStateReducer = (state = defaultVideoState, action) => {   console.log('VideoStateReducer','state',state,'action', action);   switch(action.type){     case Actions.SET_SOURCE:       return {         ...state,         status: Status.NEW_SOURCE,         source: action.url       }     case Actions.PLAY: \/\/ handel action PLAY di sini       return {         ...state,         \/\/update state.status         status: Status.PLAY_REQUESTED       }     case Actions.PAUSE: \/\/handel action PAUSE       return{         ...state,         status: Status.PAUSE_REQUESTED       }   }   return state; };<\/code><\/pre>\n\n\n\n<p>Update&nbsp;<code>src\/video_wrapper.js<\/code>, tambahin blok&nbsp;<em>else-if<\/em>&nbsp;untuk handel&nbsp;<code>PLAY_REQUESTED<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>store.subscribe(() => {     const state = store.getState();     if (state.status === Status.NEW_SOURCE) {       videoEl.src = state.source;     } else if (state.status === Status.PLAY_REQUESTED) {       \/\/blok if baru utk handle status PLAY_REQUESTED       if (videoEl.src) {         \/\/kalo source udah diset, maenin videonya         videoEl.play();       }     } else if(state.status === Status.PAUSE_REQUESTED){       if(videoEl.src) {         \/\/pause videonya         videoEl.pause();       }     }   });<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Play \u2013 Pause UI<\/h4>\n\n\n\n<p>Lanjutin bikin UI. Tombol Play bukan cuma untuk&nbsp;<em>play<\/em>, tapi juga dipake untuk&nbsp;<em>pause<\/em>. Jadi kita bikin file&nbsp;<code>src\/ui\/playpause-btn.js<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/ui\/playpause-btn.js import * as Status from '..\/status'; import { play, pause } from '..\/actions'; const PlayPauseBtn = (store) => {   const btn = document.getElementById('play-pause-btn');   btn.addEventListener('click', (e) => {     e.preventDefault();     const status = store.getState().status;     if (status === Status.PLAYING) {       store.dispatch(pause());     } else {       store.dispatch(play());     }   })   \/\/update label   store.subscribe(() => {     const state = store.getState();     if (state.status === Status.PLAYING) {       btn.innerHTML = 'Pause';     } else if(state.status === Status.PAUSED) {       btn.innerHTML = 'Play';     }   }) }; export default PlayPauseBtn;<\/code><\/pre>\n\n\n\n<p>Ternyata kita belum punya action untuk update video state ( PLAYING &amp; PAUSE ). Jadi bikin dulu generatornya:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>\/\/file: src\/actions.js export const VIDEO_STATUS_CHANGED = 'VIDEO_STATUS_CHANGED' \/\/...scroll ke bawah \/\/bikin action generator export const videoStatusChanged = ( status )=>({   type: VIDEO_STATUS_CHANGED,   status })<\/code><\/pre>\n\n\n\n<p>Nanti yang dispatch action ini adalah&nbsp;<code>VideoWrapper<\/code>&nbsp;karena dia yang punya elemen&nbsp;<code>&lt;video&gt;<\/code>, jadi kita update dulu:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/file: src\/video_wrapper.js\n\nimport * as Status from '.\/status';\n\/\/import action yg baru\nimport { videoStatusChanged } from '.\/actions';\n\nconst VideoWrapper = (store) => {\n  const videoEl = document.getElementById('myvideo');\n  \/\/pasang listener buat 'playing' event\n  videoEl.addEventListener('playing', (e) => {\n    store.dispatch(videoStatusChanged(Status.PLAYING));\n  });\n  \/\/pasang listener buat 'pause' event\n  videoEl.addEventListener('pause', (e) => {\n    store.dispatch(videoStatusChanged(Status.PAUSED));\n  });\n\n  \/\/ ...dsb\n\n  });\n\n}\n\nexport default VideoWrapper;\n\n\nTime Label\nUntuk update time-label ( yang nunjukin waktu sekarang &amp; durasi video), kita bikin dulu actionnya.\n\n\/\/file: src\/actions.js\n\n\/\/...dsb\nexport const UPDATE_TIME = 'UPDATE_TIME';\n\n\/\/... dsb\n\/\/scroll ke bawah\n\nexport const updateTime = ({time,duration})=>({\n  type: UPDATE_TIME,\n  time,\n  duration\n});Copy\nTerus update reducer.\n\nconst videoStateReducer = (state = defaultVideoState, action) => {\n  \/\/ ...dsb\n  switch(action.type){\n    \/\/...dsb\n    \/\/bikin case baru buat action UPDATE_TIME\n    case Actions.UPDATE_TIME:\n      return {\n        ...state,\n        time: action.time,\n        duration: action.duration\n      }\n  }\n  return state;\n};Copy\nSekarang bikin modul UI nya.\n\n\/\/file: src\/ui\/time-ui.js\nimport * as Status from '..\/status';\n\nconst TimeUI = (store)=>{\n\n  const timeInput = document.querySelector('input[name=time]');\n  const duration = document.getElementById('duration');\n\n  store.subscribe(()=>{\n    const state = store.getState();\n    \/\/update time &amp; duration\n    timeInput.value = state.time;\n    duration.innerHTML = state.duration;\n  })\n};\n\nexport default TimeUI;\n\nSeek \/ Scrub\nKenapa time label saya bikin pake &lt;input>? Karena mau saya pake untuk seeking\/scrubbing video.\n\nKayak yg sebelumnya, bikin reducernya dulu:\n\n\/\/file: src\/index.js\nconst videoStateReducer = (state = defaultVideoState, action) => {\n\n  switch(action.type){\n    \/\/... dsb\n    case Actions.SEEK:\n      return{\n        ...state,\n        status: Status.SEEK_REQUESTED,\n        time: action.time\n      }\n  }\n  return state;\n};Copy\nBikin juga konstanta buat status nya:\n\n\/\/file: src\/status.js\n\/\/...dsb\nexport const SEEK_REQUESTED = 'seek_requested';Copy\nKembali ke modul TimeUI. Kita pake &lt;input> untuk seeking. Jadi kita ketikin mau forward\/rewind ke detik ke berapa. Pas input-nya nggak fokus ( focusout) , modul ini kirim action SEEK_REQUESTED.\n\n\/\/file: src\/ui\/time-ui.js\nimport * as Status from '..\/status';\nimport { seek } from '..\/actions';\n\nconst TimeUI = (store) => {\n\n  \/\/----- HANDEL FOCUS IN &amp; OUT -----\/\/\n\n  \/\/buat simpan value yang lama sebelum fokus\n  let valueBeforeFocused = undefined;\n\n  const timeInput = document.querySelector('input[name=time]');\n  timeInput.addEventListener('focusin', () => {\n    \/\/nilai yang lama sebelum focus\n    valueBeforeFocused = timeInput.value;\n  });\n\n  timeInput.addEventListener('focusout', () => {\n    \/\/kalo value yang baru nggak sama dengan yang lama\n    if (timeInput.value != valueBeforeFocused) {\n      \/\/dispatch seek action\n      store.dispatch(seek(timeInput.value));\n    }\n  });\n  const duration = document.getElementById('duration');\n\n  \/\/... dsb\n\n};\n\nexport default TimeUI;Copy\nKarena value diupdate secara otomatis selama video berjalan, kita perlu stop update kalo inputnya lagi fokus (focusin). Kita bikin flag isFocused yang nilainya tergantung status focus elemen &lt;input>. Jadi kita update beberapa baris:\n\n\/\/file: src\/ui\/time-ui.js\nimport * as Status from '..\/status';\nimport { seek } from '..\/actions';\n\nconst TimeUI = (store) => {\n\n  let isFocused = false;\n\n  timeInput.addEventListener('focusin', () => {\n    \/\/set isFocused\n    isFocused = true;\n    \/\/..dsb\n  });\n\n  timeInput.addEventListener('focusout', () => {\n    \/\/reset isFocused\n    isFocused = false;\n    \/\/...dsb\n  });\n\n  \/\/...dsb\n\n  store.subscribe(() => {\n    const state = store.getState();\n    \/\/update input value kalo\n    \/\/lagi nggak fokus\n    if (!isFocused) {\n      timeInput.value = state.time;\n    }\n    duration.innerHTML = state.duration;\n  })\n};\n\nexport default TimeUI;<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Redux merupakan&nbsp;back-end&nbsp;yang berkolaborasi dengan&nbsp;react native.&nbsp;Belajar&nbsp;framework&nbsp;baru merupakan hal yang menantang dan mmanaenyusahkan, namun sekali menemukan alur pengkodean (menulis code) akan lebih mudah untuk mengimplementasikan. Menjadi seorang pemula dalam Redux membuat saya &hellip; <\/p>\n","protected":false},"author":1,"featured_media":200,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[59],"class_list":["post-199","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknologi","tag-redux"],"_links":{"self":[{"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/posts\/199","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/comments?post=199"}],"version-history":[{"count":2,"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/posts\/199\/revisions"}],"predecessor-version":[{"id":202,"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/posts\/199\/revisions\/202"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/media\/200"}],"wp:attachment":[{"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/media?parent=199"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/categories?post=199"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sentraldigitalindonesia.co.id\/blog\/wp-json\/wp\/v2\/tags?post=199"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}