This post assumes basic familiarity with redux-saga . Here’s the introductory tutorial . In this post I will examine how can be used to model some common patterns in application control-flow. redux-saga is a library that aims to make side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) in React/Redux applications easier and better. redux-saga first-amongst-these This is a where the application is simply waiting for different kinds of actions. The first action to be received decides how the application proceeds. That is . pattern the application does not care for any actions after the first one For example, consider we are writing a saga for creating and sending an email. In this saga we are waiting for 2 actions if this action is received first, the saga will discard the current draft, clean up the editor state & finish. DISCARD_DRAFT if this action is received first, then the saga will probably do some validations (e.g. valid email address etc.), send the email, clean up the editor state & then finish. SEND_EMAIL This flow of this saga is governed by which action ( or ) is received first. Such a situation can be modelled by simply using the effect creator. DISCARD_DRAFT SEND_EMAIL [take](https://redux-saga.js.org/docs/api/#takepattern) function *emailSaga() {... const action = yield take(\[ // (A) \``DISCARD_DRAFT`\`, \`SEND\_EMAIL\` \]); if (action.type === \``DISCARD_DRAFT`\`) { // (B) //discard the draft } else { //send the email } } (A) The effect waits for any one of the 2 actions and the saga is suspended until one of them is received. take (B) The saga inspects the of the received action & then proceeds accordingly. type NOTE: This situation could also be modelled using a [_race_](https://redux-saga.js.org/docs/api/#raceeffects) effect as shown below function *emailSaga() {const { discard, send } = yield race({ // (A)discard: take(`DISCARD_DRAFT`),send: take(`SEND_EMAIL`)}) if (discard) { //discard the draft } else { //send the email } } (A) We create a race between 2 effects i.e. the race ends when either one of the 2 effects is finished. take take The important semantic distinction between and is that take([...]) race waits for the . take([...]) first matching action to arrive waits for the . race first racing-effect to finish keep-doing-until This again is a common pattern where we want to keep a task running until we receive a specific action to stop the task. For example, consider we are writing a saga for adding songs to a playlist. The saga should let the user add as many songs as they like. However it should stop that task when a specific action has been received (like ). SAVE_PLAYLIST This situation can be modelled as shown below function *addToPlaylist() {while (true) { //(A)const action = yield take([`ADD_SONG`,`SAVE_PLAYLIST`]); if (action.type === \`ADD\_SONG\`) { //add the song to the playlist } else { break; //(B) } } } (A) The loop keeps the task running. while (B) As soon as is received, we break out of the loop, there by stopping the task. SAVE_PLAYLIST NOTE: This situation could also be modelled using a [_takeEvery_](https://redux-saga.js.org/docs/api/#takeeverypattern-saga-args) effect as shown below function *addToPlaylist() {const addTask = yield takeEvery(`ADD_SONG`, function*() { // (A)//add the song to the playlist}); yield take(\`SAVE\_PLAYLIST\`); // (B) yield cancel(addTask); // (C) } (A) We start a continuously running task (using ) that receives every action & adds it to the playlist. addTask takeEvery ADD_SONG (B) The saga continues & now waits for the action. SAVE_PLAYLIST (C ) Once is received the saga the i.e. it stops listening for actions. SAVE_PLAYLIST cancels addTask ADD_SONG This way of modelling the situation is more concise, however the previous way is more explicit about its intentions. step-by-step This is a common pattern where a business flow is broken down into smaller steps. These steps are presented to the user in an ordered way, however at any time the user is allowed to go back. For example, consider the process of booking a flight. It can be broken down into the following 3 steps this step is responsible for letting the user to choose a flight. Choose Flight — this step collects the necessary details from the user. Fill Details — this step is responsible for collecting the payment from the user. Payment — These steps are shown to the user in the following order Choose Flight ---> Fill Details ---> Payment However the use should also be able to go back to the previous step. ---> ---> Choose Flight Fill Details Payment<--- <--- Such a requirement can be modelled using a and multiple corresponding to each step. In essence, the controls the propagation of the process & executes the correct for the current step. parent-saga children-sagas parent-saga child-saga Consider we are writing a saga for the above mentioned process of booking a flight. We assume we have the following for the 3 steps.The contents of these are not relevant for this discussion. children-sagas children-sagas function *chooseFlight() { ... } // (A)function *fillDetails() { ... } // (B)function *paymentSaga() { ... } // (C) We assume once a is finished, we automatically proceed to the next step. child-saga We assume that every time the user wants to go back to the previous step, a action is dispatched. BACK We will now create a that controls the propagation & executes the correct based on the current step. parent-saga child-saga function *bookFlight() { // (A)let breakLoop = false;let step = 0; // (B) const backTask = yield takeEvery(\`BACK\`, function\*() { // (C) if (step > 0) { step--; } }) while (true) { // (D) switch (step) { // (E) case 0: { yield call(selectFlight); // (F) step++; // (G) break; } case 1: { yield call(fillDetails); step++; break; } case 2: { yield call(paymentSaga); step++; break; } case 3: { breakLoop = true; // (H) yield cancel(backTask); // (I) break; } } if (breakLoop) { // (J) break; } } } (A) The is called . parent-saga bookFlight (B) Set the to i.e. start with the first step 0 child-saga selectFlight . (C ) Start a task to listen for every action and decrement the by . BACK step 1 (D) Start an infinite loop to keep the running continuously. while parent-saga (E) The statement evaluates the current and executes the correct switch step child-saga. (F) Execute for . selectFlight child-saga step 0 (G) Increment the by to move to the next step. step 1 (H) If the value of is i.e. all the steps are completed, the should finish. step 3 parent-saga (I) Cancel the task to listen for actions. BACK (J) Evaluates if the should finish or continue. parent-saga NOTE:This is a trivial example where we support only single-step propagation. However in a production app, we would want the user to jump from any step to any step (with some checks of course). But this technique can be very easily extended to implement such a requirement (by explicitly providing the next value for _step_ variable, instead of incrementing or decrementing it). In fact this is how we can build a finite state machine using sagas. redux-saga is an interesting tool for modelling control-flows. I hope these patterns prove to be useful.