Next App

เราจะเริ่มต้นสร้าง app ใหม่กันชื่อว่า manager ดังนั้น ก่อนอื่นเลย ให้เข้าไปที่ terminal แล้วพิมพ์คำสั่งต่อไปนี้ที folder ที่เราใช้เพื่อสร้างโปรเจ็กท์

จากนั้นให้สร้างไฟล์ manager/.eslintrc แล้วเพิ่ม code เข้าไปดังนี้

Manager App mockup

Mockup ของ Manager App เป็นดังนี้

screen-shot-2559-12-07-at-10-17-00-am

ระบบนี้เป็นระบบสำหรับการจัดการพนักงาน รวมไปถึงการไล่ออก ประกอบด้วยหน้าจอหลัก 4 หน้าจอ อันได้แก่

  1. หน้า  Login
  2. หน้ารายชื่อพนักงานที่สามารถเพิ่มพนักงานได้
  3. หน้ารายละเอียดพนักงานที่สามารถไล่พนักงานออกได้
  4. หน้า modal popup สำหรับการไล่พนักงานออก

App Challenge

ระบบ manager นี้มีจุดยากที่เราต้องรู้ก่อนเริ่มลงมือพัฒนา

slide2

  1. เราต้องตั้งคำถามว่า LoginForm component ที่เราเคยสร้างไว้ในโปรเจ็กท์ auth นั้น ควรจะ refactor ให้เข้ามาตรฐานของ Redux หรือไม่
  2. Header component ต้องสามารถเปลี่ยนการ render text และ component ต่างๆ บนตัวมันได้แบบ dynamic ขึ้นอยู่กับว่าปัจจุบันอยู่ใน screen ไหน
  3. ผู้ใช้งานในระบบนี้คือ ผู้จัดการที่ต้องการบริหารพนักงานในมือ ดังนั้น ผุ้ใช้งานจะมีกลุ่ม employee หรือพนักงานของตนเอง
  4. หน้า popup สำหรับย้ำเตือนผู้ใช้ในกรณีที่จะไล่พนักงานออก จะต้องแสดงแบบ full screen
  5. ต้องมี navigator สำหรับการ route ไปยังหน้าจอต่างๆ

Initializing Project

ตรงส่วนนี้คืองานที่เราต้องทำซ้ำๆ กันทุกครั้งที่เริ่มต้นโปรเจ็กท์ให้ โดยให้เราสร้างไฟล์ manager/src/App.js ขึ้นมา แล้วเติม code ดังนี้

จากนั้นให้สร้างโฟลเดอร์ reducers ภายใต้ manager/src แล้วในโฟลเดอร์ดังกล่าวให้สร้างไฟล์ index.js โดยให้เพิ่ม code เข้าไปดังนี้

จากนั้นทั้ง index.ios.js และ index.android.js ให้เปลี่ยน code เป็นดังนี้

ทำให้ปัจจุบัน โครงสร้างไดเร็กทอรี่ของระบบ manager จะเป็นดังนี้

จากนั้นให้รันระบบด้วยคำสั่ง react-native run-ios หรือ react-native run-android แล้วดูว่ามันรันได้เป็นปรกติหรือไม่

Firebase setup

ให้ทำการติดตั้ง firebase module เข้าไปในโปรเจ็กท์ด้วยคำสั่งต่อไปนี้

จากนั้นให้ไปสร้าง firebase project ที่ http://console.firebase.google.com/ จากนั้นก็ให้ทำตามขั้นตอนการ setup project ตามหัวข้อ Firebase Project Creation ใน React Native Tutorial ตอนที่ 6 โดยตั้งชื่อโปรเจ็กท์ว่า manager เมื่อได้ตัว code สำหรับ setup firebase มาแล้ว ก็นำมาใช้ใน App.js ดังนี้

หมายเหตุ: ในกรณีที่เกิด error แจ้งว่าเรายังไม่ได้ติดตั้ง firebase ให้เราทำการ restart emulator และ terminal อาจจะช่วยแก้ปัญหาได้

Refactoring LoginForm

หลักคิด

จริงๆ แล้วเราไม่จำเป็นต้องเปลี่ยน LoginForm ให้มาใช้ Redux ก็ได้ เพราะเขียนด้วย React 100% แบบเดิมก็ทำงานได้อยู่แล้ว เพียงแต่ว่า การใช้ Redux นั้นจุดประสงค์ก็เพื่อช่วยให้ในระยะยาวแล้ว เมื่อถึงคราวที่ complexity ของระบบมันมากขึ้น Redux จะเข้ามาช่วยจัดระเบียบการทำงานให้

ดังนั้น สำหรับแบบฝึกหัดนี้ ถึงแม้ LoginForm จะไม่ได้มีความ complexity ขนาดนั้น แต่เราจะลองมาฝึกเปลี่ยนให้ component ตัวหนึ่งหันมาใช้ Redux กัน

ขั้นต้นเรามาดูกันก่อนเลยว่า ภายใน LoginForm นั้นมี state อยู่กี่ตัว

loginformstate

หากยังจำกันได้ LoginForm ใน auth project มี props อยู่สี่ตัว (ซึ่งเราจะนำมาเป็น state ใน redux) ได้แก่ email, password, error, loading

ที่ผ่านมา ใน LoginForm จะทำหน้าที่ทั้งในแง่ของ logic และการ render ui ดังรูป

screen-shot-2559-12-07-at-7-58-54-pm

แต่หากเราต้องการจะ refactor ให้ LoginForm ทำงานตามหลัก Redux เราจะต้องแยกงานของส่วน logic ออกมาต่างหาก แล้ว LogicForm จะทำหน้าที่ในส่วนของการ render หน้าจอเพียงอย่างเดียว ดังรูป

screen-shot-2559-12-07-at-7-59-51-pm

คราวนี้ LoginForm ก็จะรับผิดชอบเพียงแค่ส่วนของการแสดง UI ส่วนพวก event handler และ action creator ทั้งหลายอาจจะถูกเรียกใช้ภายใน LoginForm แต่จะถูก implement ภายใน Redux

คราวนี้มาดูกันต่อว่า ระหว่าง React กับ Redux นั้นทำงานร่วมกันอย่างไร

screen-shot-2559-12-07-at-8-00-42-pm

เริ่มต้น เวลาที่ user มีกิจกรรมใดๆ กับระบบ ตรงนั้น React จะรับผิดชอบไป เช่น การ tab, การกรอกข้อมูล แต่หลังจากนั้น ทุกอย่างจะวิ่งเข้าไปที่ Redux ทั้งหมด แล้วเมื่อ redux ทำการแก้ไข state แล้ว ก็จะส่งไม้กลับไปที่ React ให้ทำการ render หน้าจอใหม่ตาม state ที่เปลี่ยนไป

หากเอาแผนภาพข้างต้นมาลงรายละเอียด flow มันจะวิ่งดังภาพต่อไปนี้

screen-shot-2559-12-08-at-7-11-08-am

อันนี้เป็นตัวอย่างของภาพรวมในตอนที่ user พิมพ์ตัวอักษรลงไปในช่อง email หรือ password จากนั้นตัว component จะเรียกใช้ action creator (ท่อนำฝาก) เพื่อนำ action (สลิปฝาก/ถอน และเงินสด) ไปส่งให้กับ reducer (พนักงานแบงค์) จากนั้น reducer ก็จะประมวลผลเพื่อสร้าง state ใหม่ แล้วส่ง state ดังกล่าวคืนกลับไปให้กับ component ทุกตัว จากนั้นในขั้นตอนสุดท้าย React จะ render หน้าจอใหม่โดยอาศัยฐานบนข้อมูลบน state ใหม่ดังกล่าว

ลง code กัน

ก่อนอื่นให้ทำการ copy ไฟล์ทั้งหมดจาก tech_stack/src/components/common/*.* มาไว้ที่ manager/src/components/common/ (ให้สร้าง folder common ไว้ก่อน)

จากนั้นให้สร้างไฟล์ manager/src/components/LoginForm.js ขึ้นมาแล้วใส่ code ไปดังนี้

จากนั้นให้เปิดไฟล์ manager/src/App.js ขึ้นมาแล้วแก้ไฟล์ดังนี้

ตอนนี้โครงสร้าง directory ของเราจะเป็นดังนี้

ให้ทดสอบโดยการเปิด emulator ขึ้นมาเพื่อดูว่า มันจะแสดงผลดังรูปหรือไม่

screen-shot-2559-12-08-at-7-04-04-am

หมายเหตุ: เรายังไม่ได้ใส่ระบุ onPress property ให้กับ Button component หน้าจอจึงโชว์ warning อย่างที่เห็น 

จัดระเบียบสังคม

เวลาที่เราจะจัดโครงสร้าง code ให้ตรงตาม pattern ที่ต้องมีการแยก folder และไฟล์ให้มันกระจายไปตามที่ต่างๆ นั้น เขาเรียกกันว่า boilerplate และตอนนี้เราจะมาเริ่มจัดแจง boilerplate ให้กับระบบกัน

เริ่มต้นจากสร้าง reducer ตัวใหม่ขึ้นมาชื่อว่า AuthReducer เพื่อใช้ทำงานสำหรับการทำ authentication ภายใน LoginForm โดยให้สร้างไฟล์ manager/src/reducers/AuthReducers.js แล้วเติม code ดังนี้

เป็น reducer ที่ยังไม่ทำอะไรมาก รับ state อะไรมา ก็ return ไปอย่างนั้น (คล้ายกับรับพนักงานมาก่อน แต่ยังไม่ได้ assign งานอะไรให้)

สังเกตมั๊ยครับว่า เวลาเราสร้าง reducer ทุกครั้ง เราจะต้องใส่ค่า default ให้กับ state ซึ่งเป็น argument ตัวแรกของ reducer ทุกครั้ง เพราะตอนเริ่มต้น state จะมีค่าเป็น undefined และ Redux จะไม่ยอมให้ reducer รับ state ที่มีค่าเป็น undefined ดังนั้นเราจึงต้องเซตค่า default ให้กับ argument ตัวนี้

จากนั้นก็เอา AuthReducer ตัวนี้ไป combine รวมเป็น Reducer ใหญ่ (เหมือนพนักงานทั้งแผนก) ภายใน combindReducers ดังนั้นให้เปิดไฟล์ manager/src/reducers/index.js แล้วแก้ code ตามนี้

นั่นคือ เรากำหนดให้ state ของ Redux ที่ชื่อว่า auth เป็นผลจากการทำงานของ AuthReducer (เปรียบแล้วก็คือ พนักงานชื่อ AuthReducer มีหน้าที่ในการส่งรายงานที่ชื่อ auth เพื่อใช้ส่งต่อไปยังส่วนต่างๆ ภายในธนาคารนั่นเอง)

คราวนี้ให้เราสร้าง action creator (ท่อนำฝาก) ขึ้นมาหนึ่งตัวเพื่อใช้รองรับ event onTextChange ของ email field ใน LoginForm โดยให้สร้างไฟล์ manager/src/actions/index.js ขึ้นมา (ให้สร้างโฟลเดือน manager/src/actions ขึ้นมาก่อน) แล้วเพิ่ม code เข้าไปดังนี้

จากนั้นให้นำ action creator ตัวนี้มาใช้ภายใน LoginForm โดยให้เปิดไฟล์ manager/src/components/LoginForm.js ขึ้นมาแล้วปรับ code ดังนี้

แน่นอนครับว่า เมื่อไหร่ที่เราต้องการใช้ action creator เราก็ต้องใช้งาน redux ผ่าน connect แต่คราวนี้เรายังไม่ได้ใช้งาน redux’s state ดังนั้น argument ตัวแรกภายใน connect จึงเซตเป็นค่า null ไปก่อน และยิงเพียงแค่ action creator ที่ชื่อ emailChanged เข้าไปเป็น props ให้กับ LoginForm

จากนั้นให้เราลอง refresh emulator ซักครั้ง เพื่อตรวจสอบดูว่า มี error อะไรหรือไม่

ปรับแต่ง eslint ให้มองข้ามกฎบางข้อ

สังเกตมั๊ยครับว่า เวลาเราเขียน arrow function ในแบบที่ใช้ { } แล้ว eslint มันชอบแจ้ง error แบบนี้

screen-shot-2559-12-08-at-7-33-08-am

ซึ่งการเขียน arrow function แบบครอบด้วย { } และระบุ return เอาไว้นั้น มันดูอ่านง่ายกว่าการทำตาม arrow function 100% (ที่ครอบด้วยวงเล็บเฉยๆ แล้วไม่ต้องเขียนคำว่า return) ดังนั้น เราจึงอยากจะปิดกฎตัวนี้ที่ชื่อว่า arrow-body-style (จากรูป error ทุกตัวของ eslint จะบอกชื่อ rule ให้เรารู้)

วิธีการปิด rule นั้นทำได้ง่ายๆ ครับ ให้เปิดไฟล์ manager/.eslintrc ขึ้นมาแล้วแก้ code ดังนี้

การระบุค่า 0 ให้กับ rule ใดก็ตาม คือการปิด rule นั้นแล้ว

คราวนี้พอเรากลับไปที่ code arrow function ตัวเดิม จากนั้นลอง update อะไรซักนิดนึง แล้ว save ไฟล์ (เพื่อให้โปรเจ็กท์มีการ refresh) จะเห็นว่า error หายไปแล้ว ดังรูป

screen-shot-2559-12-08-at-9-28-48-am

Typed Script

สำหรับ Redux ที่เป็นระบบใหญ่ๆ นั้น การระบุชื่อ action ด้วย string ดิบๆ แบบ

หรือ

มันเสี่ยงต่อการพิมพ์ผิดอย่างมาก ดังนั้น วิธีการที่ดีที่สุดคือ ให้สร้าง constant variable ขึ้นมาซักตัวหนึ่ง แล้ววางไว้ในตำแหน่งที่ component ทุกตัว access ได้ จากนั้นก็นำ constant ตัวนั้นมาใช้

ไฟล์ที่ใช้เก็บ Action Type เราจะเรียกว่า types.js ดังนั้น ให้สร้างไฟล์นี้ขึ้นมาภายใน manager/src/actions/ แล้วเติม code เข้าไปดังนี้

จากนั้นให้เปลี่ยน code ใน manager/src/actions/index.js ดังนี้

และให้เพิ่ม code ภายใน manager/src/reducers/AuthReducers.js ดังนี้

เราเพิ่ม case EMAIL_CHANGED ขึ้นมา จากนั้นก็ return state ตัวใหม่กลับออกมา

และเมื่อ AuthReducer พร้อมใช้งานแล้ว นั่นแปลว่า ค่าของ state.email ก็พร้อมใช้งานด้วย ดังนั้นให้เราเอา state.email มาใช้ใน manager/src/components/LoginForm.js ดังนี้

เมื่อเราได้ state จาก Redux มาแล้ว เราก็ต้องทำการ mapStateToProps เพื่อฉีดเข้าไปให้กับ LoginForm ได้ใช้งาน state.email โดยเราทำการ map ให้มันเป็น props.email ภายใน LoginForm component แทน

คราวนี้ก็ให้ลอง refresh emulator ดูว่า มันยังทำงานปรกติอยู่มั๊ย

Immutable State

ก่อนจะไปต่อ ผมอยากให้คุณผู้อ่านได้ทำความเข้าใจใน concept ของ immutable เสียก่อน เพราะเป็นแก่นสำคัญสำหรับการทำความเข้าใจถึงวิธีการจัดการ state ของ React และ Redux

ก่อนอื่นอยากให้คุณผู้อ่านได้เห็นแผนภาพนี้

screen-shot-2559-12-08-at-11-45-19-am

หากยังจำกันได้ Reducer (พนักงานแบงค์) จะรับ argument สองตัว นั่นคือ state (ข้อมูลบัญชีของทั้งธนาคาร) และ action (สลิปเงินฝาก และเงินสด) ซึ่งตัว state นี้ จะเป็นข้อมูลบางส่วนของ state ทั้งหมด (อย่างเช่น state.auth ซึ่งเป็น state เฉพาะของ auth)

เมื่อ state ดังกล่าวถูกนำมาขยำรวมกับกับ action ภายใน reducer แล้ว (พนักงานแบงค์เอาสลิป และเงินฝากมาฝากเข้าบัญชีทำให้เงินบัญชีคงเหลือเปลี่ยนไป) reducer ก็จะ return ค่า state ใหม่ออกมา (ข้อมูลบัญชีที่ได้รับการแก้ไขแล้ว) จากนั้น Redux จะทำการเปรียบเทียบว่า state ใหม่ และเก่านั้น เหมือนกันหรือไม่ หากเหมือนก็จะไม่ update หน้าจอ แต่หากต่างกัน React ก็จะทำการ update หน้าจอให้

เมื่อรู้หลักการทำงานคร่าวๆ แล้ว คราวนี้มาดูวิธีการเปลี่ยนข้อมูลภายใน state แบบปรกติดูหน่อย (แบบปรกติคือเป็นแบบที่ไม่ใช้หลัก immutable) screen-shot-2559-12-08-at-11-46-47-am

จากรูป เราตั้งค่า const ชื่อ state ขึ้นมาสั่งให้ reference ไปที่ object ตัวหนึ่ง

ต่อมาเราสั่งให้ newState มีค่าเท่ากับ state

หากเรากำลังจากให้ newState.color = ‘red’ แล้ว สุดท้าย newState กับ state จะมีค่าเท่ากันหรือไม่

ให้เวลาคิดคำตอบนิดนึง

OK หมดเวลา มาดูเฉลยกัน

screen-shot-2559-12-08-at-11-47-06-am

newState กับ state นั้น ต่างก็ reference ไปที่ object ตัวเดียวกัน ดังนั้น ไม่ว่าใครก็ตามที่ไป update ค่าของ object ตัวนั้น ค่าของทั้ง state และ newState ก็จะเท่ากันเหมือนเดิม

ผลก็คือ React จะไม่ยอม render หน้าจอให้เรา เพราะมันมองว่า state ที่ return ออกมามันคือตัวเดิม และนี่คือที่มาว่า ทำไม Redux เวลาจะ update state จึงต้องใช้วิธีแบบ immutable

immutable แปลง่ายว่า ไม่สามารถเปลี่ยนแปลงได้ หมายถึง เราต้องเขียนโปรแกรมในแบบที่ไม่เข้าไปเปลี่ยนแปลง state แต่เมื่อใดที่เรามีการเปลี่ยนแปลง state เราจะ return state ตัวใหม่ออกมาเลย ไม่ใช่เข้าไปแก้ไขข้อมูลข้างใน

ดังนั้น code ในบรรทัดนี้

จึงถือเป็นการเขียนโปรแกรมแบบ immutation เพราะอย่างที่ได้เคยกล่าวเอาไว้ในเรื่องของ ES6 spread ไว้ว่า code ดังกล่าวเปรียบได้กับ code ดังต่อไปนี้

นั่นคือ นำ state และ email: action.payload มา merge แล้ว rewrite ลงไปใน object ใหม่เอี่ยม จากนั้นก็ return ออกมา

จะเห็นได้ว่า เราไม่ได้ทำการแก้ไขข้อมูลภายใน state เดิมเลย แต่เรานำมันไป merge กับค่าใหม่ แล้วสร้าง state ใหม่ขึ้นมา

นี่แหละคือเหตุผลว่า ทำไมวิธีการจัดการกับ state ภายใน redux จึงต้องใช้ syntax ประหลาดอย่าง Ojbect.assign() หรือ ES6 spread ด้วย

เข้าใจแล้วเนอะ!!!

คราวนี้เรามาดูขั้นตอนการทำงานของ Redux ก่อนจะมาเป็น this.props.email ใน LoginForm อย่างละเอียดกันหน่อย

screen-shot-2559-12-08-at-4-52-49-pm

ขั้นแรกผู้ใช้พิมพ์ข้อความลงในฟิลด์อีเมล์ จากนั้นก็จะมีการเรียก action creator ที่ชื่อ emailChanged โดยส่ง text จากฟิลด์ email แนบไปด้วย ซึ่งตัว action creator นี้จะ return action ที่มี type เป็น EMAIL_CHANGED และข้อมูลแนบ (payload) เป็นตัวอักษรภายในฟิลด์อีเมล์

เมื่อ action creator พร้อมแล้ว มันจะส่ง action ไปยัง reducer ทุกตัวที่ถูกระบุไว้ใน store ภายใน App component หาก reducer ตัวไหน มีหน้าที่ตรงกับ action ดังกล่าว (คือมีการดัก action.type ไว้) ก็จะทำการประมวลผลเพื่อสร้าง state ใหม่ขึ้นมา

จากนั้น state ตัวใหม่ จะถูกส่งไปยัง component ทุกตัว component ต่างๆ จะทำการ re-render ด้วย state ตัวใหม่ จากนั้นก็จะ input ข้อมูลที่ user กรอกเข้ามาไปยัง this.props.email

จากนั้นก็นั่งรอ event ต่อไปจาก user แล้วก็ทำงานแบบเดิมอีกวนไปวนมาไม่รู้จบ

Setup password sequence with Redux

คราวนี้เราก็ทำ step แบบเดียวกับ email แต่คราวนี้เราจะทำกับ password

เริ่มต้นจากการสร้าง type constant ก่อนเลย โดยให้เพิ่ม constant ของ action ที่แสดงให้เห็นถึงการเปลี่ยนแปลงของ password ดังนั้นให้เปิดไฟล์ manager/src/actions/types.js ขึ้นมาแล้วเพิ่ม code ดังนี้

ต่อมาก็เพิ่ม action creator โดยตั้งชื่อล้อตาม type constant แต่เปลี่ยนเป็น camelCase style เป็น passwordChanged ดังนั้น ให้เปิดไฟล์ manager/src/actions/index.js ขึ้นมา แล้วเพิ่ม code ดังนี้

เมื่อได้ action creator (ท่อนำฝาก) มาแล้ว ก็ได้เวลาแผนกที่จะรับ action creator ไปดำเนินการต่อ นั่นคือ reducer (พนักงานแบงค์) โดยคราวนี้เราจะไม่เพิ่มไฟล์ reducer (ไม่จ้างพนักงานใหม่) แต่จะเพิ่มหน้าที่ให้ reducer ตัวเดิม (เพราะพนักงานที่ชื่อ AuthReducer ก็มีงานน้อยอยู่แล้ว)

ให้เปิดไฟล์ manager/src/reducers/AuthReducers.js แล้วเพิ่ม code ดังนี้

จากนั้นก็ได้เวลาเอา action มาใช้งานใน LoginForm เสียที

ให้เปิดไฟล์ manager/src/components/LoginForm.js ขึ้นมาแล้วเพิ่ม code ดังนี้

ดูจาก code แล้วไม่มีอะไรใหม่ จึงไม่จำเป็นต้องอธิบายอะไรเพิ่มเติม

ให้ทำการ refresh emulator โดยให้ภาวนาไว้ในใจว่า อย่าได้เกิด error หากเกิด error ให้ลองไล่หาวิธีแก้ปัญหาดูนะครับ (แต่ของผมไม่เกิด error 5555)

ใช้ Action Creator จัดการกับ Asynchronous function

ถึงตรงนี้คุณผู้อ่านก็น่าจะรู้แล้วว่า Action Creator (ท่อนำฝาก) มีลักษณะอย่างไร ง่ายๆ เลยก็คือ มันคือ ฟังก์ชั่น ที่ใช้สำหรับ return action (สลิป และเงินสด) ออกมาแค่นั้น

คราวนี้ปัญหาคือ ในหน้า LoginForm นั้นจะต้องมีการทำ firebase login ด้วย และตามที่เราได้เห็นไปแล้วสำหรับ event onTextChange ทั้งในส่วนของ email และ password เรามีระเบียบว่า จะต้อง call action creator จาก event เหล่านี้ ดังนั้น ปุ่ม login ก็จะต้องเรียกใช้ action creator ด้วย

ปัญหาอยู่ตรงนี้ครับ ตามธรรมชาติของ Acton creator นั้น จะมีหน้าที่ในการ return action ออกมาเมื่อถูกเรียกใช้ แล้วจากนั้น reducer ก็จะมารับไปทำงานต่อ คราวนี้ในส่วนของการ login มันมีขั้นตอนการทำงานดังนี้

screen-shot-2559-12-08-at-5-43-28-pm

คือเริ่มจากการ call action creator จากการกดปุ่ม login จากนั้น action creator จะทำการ request ไปยัง firebase เพื่อทำการ authenticate รอจน firebase มันตอบกลับมา แล้ว action creator จึงค่อย return action ออกไป

คำถามคือ เราจะทำตามขั้นตอนนี้ได้ไง เพราะทุกครั้งที่เราเรียกใช้งาน action creator มันจะทำการ return action ทันที ไม่สามารถทำได้อย่างขั้นตอนข้างต้นได้

จึงมีผู้คิดแก้ปัญหานี้ให้กับ Redux ออกมา เป็น npm module ชื่อว่า Redux Thunk

ขั้นตอนการทำงานโดยใช้ Redux Thunk จะเป็นดังนี้

screen-shot-2559-12-08-at-6-00-48-pm

ตัว redux thunk จะเข้ามาแทรกตรงกลางก่อนขั้นตอนการ return action ภายใน action creator โดยแทนที่จะ return action ออกไป มันจะ return function ออกไป (ซึ่งก็คือ function ที่ใช้สำหรับ call firebase สำหรับกรณีนี้) และเมื่อ ฟังก์ชั่นมัน complete/resolved แล้ว ตรง .then ก็จะถูกเรียกใช้งาน (อย่าลืมเรื่อง promise นะครับ เพราะ firebase auth คือ promise) โดยภายใน .then เราจะใส่ action เข้าไปแทน แล้วตอนนั้น redux thunk จะเป็นคนยิง action ออกไปให้เพื่อให้ reducer รับช่วงต่อไป

งง อ่ะดิ งั้นเรามาลุย code กันเลยดีกว่า เขียนๆ ไป แล้วกลับมาอ่านทบทวนประโยคข้างบนอีกทีอาจจะหายงงได้

เริ่มต้นให้เราปิด emulator และตัว terminal ที่รัน emulator background ก่อน (เพราะเวลาโหลด module ใหม่ ตัว emulator มักจะโชว์ error) จากนั้นเปิด terminal แล้วรันคำสั่งต่อไปนี้

จากนั้นก็เชื่อม redux-thunk เข้ากับ redux โดยให้เปิดไฟล์ manager/src/App.js ขึ้นมาแล้วแก้ code ดังนี้

พวก module พิเศษต่างๆ ที่เข้ามาทำงานร่วมกับ redux นั้น เขาเรียกมันว่าเป็น middleware ดังนั้นเราจึงต้องใช้ฟังก์ชั่น applyMiddleware เพื่อเชื่อม redux-thunk เข้ากับ redux โดยจะทำผ่าน code ต่อไปนี้

จากตอนแรกที่ createStore รับแค่ argument ตัวเดียว คือ reducers เท่านั้น ตอนนี้เราเขียนแบบรับสามตัวแล้ว โดยตัวที่สองจะเป็นค่าของ state ตอนเริ่มต้น ซึ่งเราสามารถกำหนดค่าทั้ง email และ password ตรงนี้ก็ได้ (แต่มักไม่มีใครทำ) ส่วน argument ตัวที่สามนั้นใช้สำหรับวางพวก middleware ของ redux ทั้งหลาย ดังนั้น เราจึงวางฟังก์ชั่น applyMiddleware ที่รับ ReduxThunk ไว้ตรงนี้

จากนั้นให้เราสร้าง action creator พระเอกของเราขึ้นมา โดยให้เปิดไฟล์ manager/src/actions/index.js แล้วเพิ่ม code ดังนี้

จุดสำคัญของ action creator ที่ชื่อ loginUser นั้นคือ argument ที่ชื่อว่า dispatch ซึ่งเป็น callback function ที่ทำหน้าที่เหมือนพ่อมด เพราะเมื่อใดก็ตามที่มีการยิง dispatch ตามด้วย action object ใดๆ ก็ตาม มันจะยิงเข้าไปให้ reducer โดยตรง ดังนั้น reducer จะได้รับ action object ตัวนั้น และสามารถนำไปดำเนินงานต่อได้

เมื่อสร้าง action creator เสร็จแล้ว งานต่อไปก็ต้องเป็นการสร้าง reducer ให้เปิดไฟล์ manager/src/reducers/AuthReducers.js ขึ้นมา แล้วแก้ code ดังต่อไปนี้

งานนี้ไม่มีอะไรมากครับ แค่ดัก action ด้วย console.log เพื่อดูว่า action ที่วิ่งเข้ามานั้นมีอะไรบ้าง และดูว่า การ login ด้วย firebase จะทำได้สำเร็จหรือไม่

จากนั้นให้นำ action creator ไปใช้งานภายใน LoginForm โดยให้เปิดไฟล์ manager/src/components/LoginForm.js แล้วแก้ไข code ดังนี้

อันนี้ตรงไปตรงมาครับ เป็นการเอา action creator มารองรับ onPress event ของ Button เท่านั้น

Add dummy user to Firebase Auth

ให้เราเข้าไปที่ https://console.firebase.google.com/ ไปที่โปรเจ็กท์ manager ที่เราเพิ่งสร้างไป จากนั้น ให้คลิก Authenticate ตรงเมนูด้านซ้าย แล้วจากนั้นคลิก “เพิ่มผู้ใช้” แล้วให้เพิ่มผู้ใช้ด้วย email และ password ตามที่เราต้องการ ดังรูป

screen-shot-2559-12-08-at-6-07-22-pm

จากนั้นให้ลองรัน emulator อีกครั้ง แล้วเปิด debugger ดู หากทำสำเร็จคุณจะเห็นภาพคล้ายๆ กับของผม

screen-shot-2559-12-08-at-7-28-14-pm

นั่นคือ มันจะแสดง รายละเอียดของ action ทุกตัว รวมไปถึง ‘LOGIN_USER_SUCCESS’ ด้วย ซึ่งเป็น action ที่ต้องรอเราทำการ login เสียก่อน จากนั้น รอซักพัก ตรง console ก็จะปรากฎ action ตัวนี้วิ่งเข้ามาใน reducer (อย่าลืมว่าเราดัก console.log ภายใน reducer)

Refactoring

เมื่อตะกี้เราเพิ่งใช้ redux thunk เป็นครั้งแรก code บางตัวก็ทำแบบลวกๆ ไปหน่อย เพราะมีจุดมุ่งหมายแค่ลองใช้งานมันเท่านั้น แต่เมื่อถึงตอนนี้ได้พิสูจน์แล้วว่า มันใช้งานได้ ก็ได้เวลามาจัดระเบียบ code ให้เข้ากับรูปแบบของ Redux กันต่อ

เพิ่ม type constant สำหรับ action ของการ login success โดยเปิดไฟล์ manager/src/actions/types.js แล้วเพิ่ม code ดังต่อไปนี้

จากนั้นก็ปรับให้ action creator ใช้งานค่า type constant แทนที่จะเป็น string ดิบๆ โดยให้เปิดไฟล์ manager/src/actions/index.js ขึ้นมาแล้วปรับ code ดังนี้

จากนั้นก็ให้ทำการมอบหมายงานใหม่ตัวนี้ให้กับ AuthReducer โดยให้เปิดไฟล์ manager/src/reducers/AuthReducers.js แล้วเติม code ลงไปดังนี้

หน้าที่ของ reducer สำหรับงานนี้ก็คือ add user เข้าไปใน state นั่นเอง

จากนั้นให้ลอง refresh emulator ดูว่า มีอะไร error หรือไม่

จัดการกับ case ที่เหลือ

เรายังเขียน case ภายใน loginUser ไม่จบ เพราะยังขาด path fail และ path ของการสร้าง user account ดังนั้น ให้เปิดไฟล์ manager/src/actions/index.js ขึ้นมาแล้วปรับ code ดังนี้

จะเห็นว่า เรามีการเรียกใช้ code ดังต่อไปนี้ซ้ำกันสองที่

ดังนั้น เราจึงควร refactor โดยสร้าง function ขึ้นมาเพื่อแก้ปัญหาตรงนี้ โดยให้แก้ code ภายใน manager/src/actions/index.js ดังนี้

นอกจากนี้เรายังต้องเพิ่มเคสที่การ create user เกิด fail ขึ้นมา ซึ่งเราจะต้องสร้าง type constant ขึ้นมาก่อน เพื่อสร้าง constant สำหรับเคสนี้ ให้เปิดไฟล์ manager/src/actions/types.js ขึ้นมา แล้วเติม code ดังนี้

จากนั้นให้เปิดไฟล์ manager/src/actions/index.js ขึ้นมาแล้วแก้ code ดังนี้

แน่นอนว่า หลังจากสร้าง action creator แล้วก็ต้องสร้าง reducer ต่อ ให้เปิดไฟล์ manager/src/reducers/AuthReducers.js ขึ้นมา แล้วแก้ code ดังต่อไปนี้

ตรงนี้จะพิเศษหน่อยตรงที่ งาน LOGIN_USER_FAIL นั้นจะทำสองอย่างคือ ส่ง error message และเคลียร์ฟิลด์ password บนหน้าจอ เพื่อเหตุผลทางความปลอดภัย

จากนั้นให้เอา error ไปแสดงใน LoginForm โดยให้เปิดไฟล์ manager/src/components/LoginForm.js ขึ้นมาแล้วปรับแก้ code ดังนี้

จากนั้นเมื่อเราลองเปิด emulator ขึ้นมา แล้วลองคลิกปุ่ม login เลยโดยไม่ได้กรอกข้อมูลอะไรในฟิลด์ทั้งสอง มันจะขั้น error message ดังรูป

screen-shot-2559-12-08-at-9-09-05-pm

คราวนี้มันเกิดปัญหาตรงที่ หลังจากที่มันแสดง error message ไปแล้ว เวลาเรากรอก email และ password เข้าไป error message ดังกล่าวก็ยังไม่หายไป

วิธีแก้ก็คือเวลา login success ให้ทำการเคลียร์ค่าของ state.error ซะ ดังนั้น ให้เปิดไฟล์ manager/src/reducers/AuthReducers.js ขึ้นมา แล้วปรับ code ดังนี้

แล้วลองกลับไปเล่นที่หน้า login ใหม่ คราวนี้ปัญหาก็หายไปแล้ว

เพิ่ม Spinner ให้กับ LoginForm

อันนี้เป็นเรื่องเก่าเอามาเล่าใหม่ ดังนั้น ไม่ต้องอธิบายมาก ลุย code กันเลย

ก่อนอื่นเราต้องสร้าง type constant ขึ้นมาก่อน ให้เปิดไฟล์ manager/src/actions/types.js ขึ้นมาแล้วเพิ่ม type constant ดังนี้

จากนั้นก็ให้ปรับ code ภายใน action โดยเปิดไฟล์ manager/src/actions/index.js ขึ้นมาแล้วปรับ code ดังนี้

แปลว่า ในจังหวะที่เรา log in อยู่นั้น ระบบจะทำการ dispatch action ของ LOGIN_USER ให้กับ reducer ต่อไป

ดังนั้น ตัวต่อไปที่จะต้องแก้ก็ได้แก่ reducer นั่นเอง ให้เราเปิดไฟล์ ดังนี้ manager/src/reducers/AuthReducers.js ขึ้นมา แล้วปรับ code ดังนี้

ขั้นตอนสุดท้ายก็คือ เอาไปใช้งานจริงภายใน LoginForm โดยให้เปิดไฟล์ manager/src/components/LoginForm.js ขึ้นมาแล้วแก้ code ดังนี้

จบครับ

สำหรับใครที่อยากไปดู code ต้นแบบ สามารถไปดูได้ที่

https://github.com/himaeng/manager10.git

สรุป

บทนี้เราทำการ refactor ให้หน้า LoginForm ทำงานตามมาตรฐานของ Redux โดยนำ middleware ที่ชื่อ Redux Thunk มาช่วยในการทำงานกับ Firebase ซึ่งเป็น promise base API