<!--
Marcus: Intro, Controlling a Saga, Fazit
Moritz: Generators, Testing
Leo: Example + Demo, Advanced Use Cases
-->
<style>
img {
border: none !important;
width: 30vh;
object-fit: contain;
background: none !important;
box-shadow: none !important;
}
p code:not(.hljs),
li code:not(.hljs) {
color: #dc8c8c;
}
.reveal blockquote {
padding: 0 25px;
}
.profilepics > div {
display: flex;
justify-content: space-around;
}
.profilepics img {
border-radius: 50%;
height: 25vh;
width: 25vh;
object-fit: cover;
max-width: 100%;
}
.profilepics h2 {
font-size: 3vh;
margin-bottom: 0;
}
.profilepics h3 {
font-size: 2vh;
}
.hljs {
font-size: 80%;
}
</style>
# Redux Saga
#### Transparent Asynchronous State Changes
*React Meetup DΓΌsseldorf*
---
<!-- .slide: class="profilepics" -->
<div class="image">
<img src="https://i.imgur.com/m7xBGhd.jpg" width="300" height="300">
<h2>Leo Bernard</h2>
<h3>github.com/leolabs</h3>
</div>
<div class="image">
<img src="https://i.imgur.com/WIwJk9U.jpg" width="300" height="300">
<h2>Marcus Weiner</h2>
<h3>github.com/mraerino</h3>
</div>
<div class="image">
<img src="https://i.imgur.com/vE8DKi4.jpg" width="300" height="300">
<h2>Moritz Gunz</h2>
<h3>github.com/NeoLegends</h3>
</div>
---
### Nice to have
- ES2015
- Redux knowledge
- Test Assertions
Note:
Marcus
---
# π
## Finding a way to do async things with Redux
Note:
- Reducers are synchronous
- many async actions in a bigger webapp
---
### Cases
- Data fetching
- Data subscriptions
- Retry, Cancel, Debounce
- Rollbacks / Optimistic updates
- Integrating async actions into the redux state machine
---
# π
## Saga
---
> [...] a Saga is like a separate thread in your application that's solely responsible for side effects.
---
# πΎ
## Actor model
---
# βΈοΈ
## What Can a Saga Do?
---
- Await events
- Interoperate with Redux
- Manage other sagas
---
# π
## Generators
Note:
Moritz
- Wer kennt das?
---
```javascript
function* generator() { /* ... */ }
const iterator = generator() // => Object
```
```javascript
iterator.next() => { value: T | undefined, done: bool }
```
<!-- .element: class="fragment" -->
```javascript
for (const item of iterator) { // calls iterator.next()
console.log(item)
}
```
<!-- .element: class="fragment" -->
Note:
- Generator returns iterator
- Object to traverse a collection
- works with for...of
- function only runs on call to .next()
---
```javascript
function* generator() {
const num = 2
// Pass value to callee and pause execution
yield 1
}
```
```javascript
const iterator = generator()
```
Note:
- Yield -> resumable function with context
- Try-catch / if-else, etc.
- Push values into generator
---
```javascript
function* generator() {
const num = 2
// Pass value to callee and pause execution
yield 1
}
```
```javascript
const iterator = generator()
// Generator progress controlled from outside
iterator.next().value == 1
```
Note:
- Yield -> resumable function with context
- Try-catch / if-else, etc.
- Push values into generator
---
```javascript
function* generator() {
const num = 2
// Pass value to callee and pause execution
yield 1
// Callee can pass values into generator
let a = yield 2
}
```
```javascript
const iterator = generator()
// Generator progress controlled from outside
iterator.next().value == 1
iterator.next().value == 2
```
Note:
- Yield -> resumable function with context
- Try-catch / if-else, etc.
- Push values into generator
---
```javascript
function* generator() {
const num = 2
// Pass value to callee and pause execution
yield 1
// Callee can pass values into generator
let a = yield 2
// `num` maintains value across yield points
yield a + num
}
```
```javascript
const iterator = generator()
// Generator progress controlled from outside
iterator.next().value == 1
iterator.next().value == 2
iterator.next(10).value == 12
```
Note:
- Yield -> resumable function with context
- Try-catch / if-else, etc.
- Push values into generator
---
```javascript
function* generator() {
const num = 2
// Pass value to callee and pause execution
yield 1
// Callee can pass values into generator
let a = yield 2
// `num` maintains value across yield points
yield a + num
}
```
```javascript
const iterator = generator()
// Generator progress controlled from outside
iterator.next().value == 1
iterator.next().value == 2
iterator.next(10).value == 12
iterator.next().done == true
```
Note:
- Yield -> resumable function with context
- Try-catch / if-else, etc.
- Push values into generator
---
# β¨
## Effects
Note:
Leo
- "Glue" between generators and saga library
- Way to control sagas
---
```javascript
function* saga() {
yield put({
type: "SET_JOKE",
payload: "What do you call a fake noodle? An impasta!",
})
}
```
```javascript
saga().next() == {
PUT: {
action: {
type: "SET_JOKE",
payload: "What do you call a fake noodle? An impasta!",
}
}
}
```
<!-- .element: class="fragment" -->
Note:
- Functions don't do anything themselves
- Return an object containing instructions for saga
- Saga receives instructions and acts on them
---
# π»
## Example
---
```javascript
function* fetchJoke() {
const { joke } = yield call(
fetchJson,
"https://icanhazdadjoke.com/",
)
yield put({ type: 'SET_JOKE', payload: joke })
}
```
```javascript
function* jokeThread() {
while (true) {
const action = yield take('GET_JOKE')
yield fork(fetchJoke)
}
}
```
<!-- .element: class="fragment" -->
Note:
- Why infinite loop?
- Alright, because function is non-blocking
---
## DEMO
https://codesandbox.io/s/1q5856pww4
---
# π
## Why is Saga so great?
Note:
Marcus
---
### Model the Whole Story of Your App
**App Lifecycle**
Login β Interaction β Logout β Login ...
```javascript
function* app() {
while (true) {
yield waitForlogin()
const pid = yield fork(runDataFetching)
yield waitForLogout()
yield cancel(pid)
}
}
```
<!-- .element: class="fragment" -->
---
# β
## Testing our Saga
Note:
Moritz
---
```javascript
it("should fetch a joke and store it in the state", () => {
const jokeMock = {
joke: "Chuck Norris knows the last digit of pi."
}
const it = fetchJoke()
})
```
---
```javascript
it("should fetch a joke and store it in the state", () => {
const jokeMock = {
joke: "Chuck Norris knows the last digit of pi."
}
const it = fetchJoke()
it.next() // returns { CALL: { ... } } effect object
})
```
---
```javascript
it("should fetch a joke and store it in the state", () => {
const jokeMock = {
joke: "Chuck Norris knows the last digit of pi."
}
const it = fetchJoke()
it.next() // returns { CALL: { ... } } effect object
expect(it.next(jokeMock).value).toEqual(put({
type: "SET_JOKE",
payload: jokeMock.joke,
}))
})
```
---
```javascript
it("should fetch a joke and store it in the state", () => {
const jokeMock = {
joke: "Chuck Norris knows the last digit of pi."
}
const it = fetchJoke()
it.next() // returns { CALL: { ... } } effect object
expect(it.next(jokeMock).value).toEqual(put({
type: "SET_JOKE",
payload: jokeMock.joke,
}))
expect(it.next())
.toEqual({ done: true, value: undefined })
})
```
---
# π‘
## Advanced use cases
Note:
Leo
---
#### Debounce
```javascript
function* handleInput({ payload }) {
yield delay(500)
const res = yield call(
fetch,
"https://myAPI",
{ body: payload },
)
}
function* watchInput() {
yield takeLatest('INPUT_CHANGED', handleInput)
}
```
Note:
- Great for input fields that trigger searches
- Limit the amount of requests
---
#### Retries
```javascript
function* request() {
while (true) {
const res = yield call(fetch, "https://myAPI")
if (res) {
put(...)
return
}
yield delay(5000)
}
}
```
Note:
- Good for flaky connections
---
# π
## Conclusion
---
- Actor Model for the Frontend<!-- .element: class="fragment" -->
- Declarative π<!-- .element: class="fragment" -->
- Sagas are pure (Runtime)<!-- .element: class="fragment" -->
- Enables sequential running of effects<!-- .element: class="fragment" -->
- Easy error handling<!-- .element: class="fragment" -->
---

## [github.com/Festify/app](https://github.com/Festify/app)
Note:
Leo
---
# β€οΈ
## Thank you!
---
## Saga "Without" Redux
```javascript
const io = {
dispatch(action) => /* saga dispatches an action */,
getState() => /* saga requests app state */,
subscribe(callback) => /* on action: callback(action) */,
}
runSaga(io, saga)
```
{"metaMigratedAt":"2023-06-14T18:42:25.653Z","metaMigratedFrom":"YAML","title":"Redux Saga","breaks":true,"slideOptions":"{\"transition\":\"fade\",\"controls\":false}","contributors":"[{\"id\":\"53c655a0-3881-4259-afc8-68b5fcfce041\",\"add\":9093,\"del\":57}]"}