@adamterlson
slides.com/adamterlson
www.internations.org/career
jobs@internations.org
Building Apps with React
Component Reuse & Storybook
Functional UI
Performance Optimization
Building Apps with Redux Saga
Advanced Patterns
Custom Channels & Buffers
Exception handling
Integration & Unit Testing
@adamterlson
adam.terlson@gmail.com
ASP
ASP.NET
.NET MVC
NODE + SPA
Data
Business Logic
Presentation Logic
Display
Backend
Frontend
Data
Business Logic
Presentation Logic
Display
REST
Backend
Frontend
Data
Data
Business Logic
Presentation Logic
Display
Backend
Frontend
REST
State management
Suspend/Resume
API data
Native linking
Push notifications
Camera
Hardware buttons
Gestures
Geolocation
Permissions
Navigation
Toasts
Lightboxes
Modals
Gestures
State management
Lightboxes
API Data
Toasts
Navigation
On Swipe
Check for draft
Ask user to save draft
If yes, persist to BE
If saved, show success message
Navigate
Debuggable
Testable
Isolated
Loosely coupled
npm downloads (source: npmtrends.com)
Do all work required upon a side effect
The King of Redux Saga
// Long running function
while (true) {
// Side effect
yield take('EFFECT_NAME')
// Describe...
}
while (true) {
yield take('OFFLINE')
yield race([
fork(doEstablishConnection),
take('ONLINE'),
])
yield put({
type: 'TOAST_SHOW',
payload: 'Back online!'
})
}
while (true) {
const paymentData = yield take('CHECKOUT')
yield call(appleStorePayments, paymentData)
yield put({ type: 'TOAST_SHOW', payload: 'Success!' })
}
while (true) {
const paymentData = yield take('CHECKOUT')
const valid = yield call(validateReceipt, paymentData)
if (!valid) {
yield put(receiptInvalid())
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
yield call(appleStorePayments, paymentData)
yield put({ type: 'TOAST_SHOW', payload: 'Success!' })
}
while (true) {
const paymentData = yield take('CHECKOUT')
try {
const valid = yield call(validateReceipt, paymentData)
if (!valid) {
yield put(receiptInvalid())
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
} catch (ex) {
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
try {
yield call(appleStorePayments, paymentData)
} catch (ex) {
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
yield put({ type: 'TOAST_SHOW', payload: 'Success!' })
}
while (true) {
try {
const paymentData = yield take('CHECKOUT')
try {
const valid = yield call(validateReceipt, paymentData)
if (!valid) {
yield put(receiptInvalid())
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
} catch (ex) {
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
let receipt
try {
receipt = yield call(appleStorePayments, paymentData)
} catch (ex) {
try {
const backupReceipt = yield call(backupPaymentAttempt)
receipt = backupReceipt
} catch (ex2) {
yield put({ type: 'TOAST_SHOW', payload: 'Backup Fail!' })
continue
}
yield put({ type: 'TOAST_SHOW', payload: 'Primary Fail!' })
}
yield put({ type: 'TOAST_SHOW', payload: 'Success!' })
} catch (ex) {
logger.log('GOD HELP ME')
}
}
while (true) {
try {
const paymentData = yield take('CHECKOUT')
try {
const valid = yield call(validateReceipt, paymentData)
if (!valid) {
yield put(receiptInvalid())
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
} catch (ex) {
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
let receipt
try {
receipt = yield call(appleStorePayments, paymentData)
} catch (ex) {
try {
const backupReceipt = yield call(backupPaymentAttempt)
receipt = backupReceipt
} catch (ex2) {
yield put({ type: 'TOAST_SHOW', payload: 'Backup Fail!' })
continue
}
yield put({ type: 'TOAST_SHOW', payload: 'Primary Fail!' })
}
yield put({ type: 'TOAST_SHOW', payload: 'Success!' })
} catch (ex) {
logger.log('GOD HELP ME')
}
}
A chunk of dangerous work (i.e. saga utility)
No while(true)
type Transaction<T> =
|{
payload: T,
error: false,
}
|{
payload: Error,
error: true
}
while (true) {
const paymentData = yield take('CHECKOUT')
const validation: Transaction<Boolean> = yield call(
validateReceipt, paymentData
)
if (!validation.payload || validation.error) {
yield put(receiptInvalid())
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
const payment: Transaction<Receipt> = yield call(
appleStorePayments, paymentData
)
if (!payment.error) {
const backupPayment: Transaction<Receipt> = yield call(
backupPaymentAttempt
)
if (backupPayment.error) {
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
}
}
yield put({ type: 'TOAST_SHOW', payload: 'Success!' })
}
while (true) {
const paymentData = yield take('CHECKOUT')
const validation: Transaction<Boolean> = yield call(
validateReceipt, paymentData
)
if (!validation.payload || validation.error) {
yield put(receiptInvalid())
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
continue
}
const payment: Transaction<Receipt> = yield call(
appleStorePayments, paymentData
)
if (!payment.error) {
const backupPayment: Transaction<Receipt> = yield call(
backupPaymentAttempt
)
if (backupPayment.error) {
yield put({ type: 'TOAST_SHOW', payload: 'Failure!' })
}
}
yield put({ type: 'TOAST_SHOW', payload: 'Success!' })
}
Controls when The Watcher watches
Arrange work for Watcher
Plumber
yield fork(watchAddContact)
yield fork(watchCreateChat)
yield fork(watchSendMessage)
while (true ){
const chan = yield channel(makeReqChannel)
const existingRequests = yield select(
state => state.requests
)
for (const req in existingRequests) {
yield put(chan, existingRequests)
}
yield fork(watchRequests, chan)
}
const pushChan = yield call(makePushChannel);
const connectivityChan = yield call(
makeConnectivityChannel
);
while (true) {
try {
yield race([
fork(watchPush, pushChan),
fork(watchConnectivity, connectivityChan),
fork(watchUserInput)
]);
} catch (ex) {
console.error("ERROR", ex);
}
}
"Root Saga"
The Watcher
The Transaction
The Forker
Take
Put
Call
Fork
Race
(WCGW)
@adamterlson
Try Redux Saga