Redux saga v/s redux thunk or both?
Tags: redux
Published: 2023-06-29 11:37:42

> Note: If you have not used redux-saga then you might not understand the problem we are going to discuss.

## Introduction

Welcome to the world of Redux middleware! If you've been exploring articles discussing the comparisons between [redux-saga](https://redux-saga.js.org/) and [redux-thunk](https://redux.js.org/usage/writing-logic-thunks#what-is-a-thunk), you've likely encountered various perspectives. While redux-saga offers unique advantages, personally, I find the simplicity of redux-thunk quite appealing.

I have experimented with both approaches individually, and I have come to the conclusion that utilizing sagas provides a greater degree of control and capability. However, there is one crucial element lacking in sagas, which is the ability to await actions like thunks. There are instances where it is necessary to be aware of when a saga execution has finished or encountered an error. For example, you may want to conceal a loading indicator at the appropriate time.

I have developed a compact Redux middleware named [redux-thaga](https://www.npmjs.com/package/@hvish/redux-thaga), which combines the functionalities of both redux-thunk and redux-saga. The name may appear lighthearted, as it is a fusion of "thunk" and "saga," resulting in "redux-thaga."

## Usage

We can create a thaga action as follows:

```js
// create the action
export const fetchTasks = createThagaAction(
  'fetchTasks',
  function* fetchTasksWorker({ status }) { // arguments: (actionPayload, action, ...restArgs)
    const tasks = (yield call(taskApi, status)) as Task[];
    return tasks;
  }
);

function* rootSaga() {
  yield takeLatest(fetchTasks, fetchTasks.worker);
}

// I am using react but you can use it with any framework or library
const TaskList = () => {
  const dispatch = useDispatch();

  const { isFetchingTasks, setIsFetchingTasks } = useState(false);
  const { tasks, setTasks } = useState([]);
  
  const fetchTasks = async (status: 'pending' | 'completed') => {
    try {
      setIsFetchingTasks(true);
      // you can await following action like thunks! 🎉
      const tasks = await dispatch(fetchTasks({ status }));
      setTasks(tasks);
    } catch (error) {
      console.log(error);
    } finally {
      setIsFetchingTasks(false);
    }
  }

  const fetchPendingTasks = () => fetchTasks('pending');
  const fetchCompletedTasks = () => fetchTasks('completed');

  return (
    <div>
      <button onClick={fetchPendingTasks}>Fetch pending tasks</button>
      <button onClick={fetchCompletedTasks}>Fetch completed tasks</button>
      {isFetchingTasks && <div>Fetching tasks...</div>}
      {tasks.map(task => <div key={task.id}>{task.title}</div>)}
    </div>
  )
}
```

The `fetchTasks` action will have three associated actions along with a saga as its properties:

1. `worker` - saga
2. `finished` - action
3. `failed` - action
4. `cancelled` - action

When `worker` is successfully executed, the `finished` action is automatically dispatched. Similarly if the `worker` encountered an unhandled error then `failed` action will be dispatched.

The `cancelled` is a special action which is dispatched only when the saga is cancelled like how `takeLatest` does for previously uncompleted saga.

Now you may think what is the point of converting a saga into a thunk, we could have used both redux-thunk along with redux-saga!

Yes we can, only the problem is we can't fork or do some saga thing inside the body of a thunk as it's not a generator function. We have to dispatch another action for it.

Please note that I am not suggesting to create all the sagas this way, but you can use it when you need _await-ability_ along with saga features.

You can see the source code [here](https://github.com/HVish/redux-thaga) on github. Please consider giving it a star if you like it.

Lastly, I would greatly appreciate and warmly welcome any suggestions you might have.

Cheers 👋