Redux saga v/s redux thunk or both?
Vishnu Singh
Posted on June 29, 2023
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 and redux-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, 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:
// 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:
-
worker
- saga -
finished
- action -
failed
- action -
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 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 đź‘‹
Posted on June 29, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.