Redux Saga

 

mobile first
with

@adamterlson

slides.com/adamterlson

www.internations.org/career

jobs@internations.org

Two Day Workshop

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

My background
is on the web

 

ASP

ASP.NET

.NET MVC

NODE + SPA

 

Making

mobile apps

is harder

Data

Business Logic

Presentation Logic

Display

Backend

Frontend

ASP.NET

Data

Business Logic

Presentation Logic

Display

REST

Backend

.NET MVC

Frontend

Data

Data

Business Logic

Presentation Logic

Display

Backend

MOBILE APPS

Frontend

REST

"Side Effects"

State management

Suspend/Resume

API data

Native linking

Push notifications

Camera

Hardware buttons

Gestures

Geolocation

Permissions

Navigation

Toasts

Lightboxes

Modals

Side Effect Combo Special!

Side Effect Type

Gestures

State management

Lightboxes

API Data

Toasts

Navigation

Requirements

On Swipe

Check for draft

Ask user to save draft

If yes, persist to BE

If saved, show success message

Navigate

0

1

3

6

10

10

"ARCHITECTURE"

💪

Reactive Manafesto

  1. Responsive
  2. Resilient
  3. Elastic
  4. Message-driven

Message-Driven Architecture

 

Debuggable

Testable

Isolated

Loosely coupled

Message-Driven Architecture

Redux

It's not about state
It's about SIDE EFFECTS

The most important decision is not picking React. 

It's whether to use Redux

Who cares because... GraphQL!

"Architecture"

out of the box

👍

Redux is here to stay

npm downloads (source: npmtrends.com)

Who cares because... HOOKS!

Application State

!==

Component State

A (re)Introduction to Redux Saga

"A long-running function that completely describes a side effect"

"The Watcher"

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!' 
    })
}

The Watcher is a script.

Go Long.

Keep it pure.

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')
    }
}

"The Transaction"

A chunk of dangerous work (i.e. saga utility)

No while(true)

Transaction:

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!' })
}

"The Forker"

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"

Saga Starter Kit

The Watcher

The Transaction

The Forker

Take

Put

Call

Fork

Race

Writing Sagas

LIVE

 

(WCGW)

Thank you

@adamterlson

Mobile first with Redux Saga

By Adam Terlson

Mobile first with Redux Saga

  • 1,055