You have begun building your React application. Maybe you even have set it up with Redux. Now you need some content - but how do you fetch it?
Fetching data from an API in React can be done quite simple. JavaScript now comes with the built in Fetch API, which gives you access to a fetch method. So if you want to make a request to an API, it can be done like this:
componentDidMount() {
fetch('https://mydomain/api/customers')
.then(response => response.json())
.then(data => this.setState({ data }));
}
Great! Now your component request some data and puts the response in your component's state. The data however is only accessible for the component fetching it, and possibly its sub components if passed as props. In many applications you will find that it would be convenient if the data was accessible throughout the entire application. Luckily, we built our application with Redux, and thus have access to several handy libraries to help out. Two common ones are redux-thunk and redux-saga. In this article we will have a closer look at redux-saga.
Redux-saga
Redux-saga is a Redux middleware library which makes use of the ES6 feature generators, which in short is a way to control the ES6 iterator. As opposed to the first example, where the component itself requested data from an API, redux-saga provides a separate "thread" which handles the side effects of your application (this can be more than just data fetching, but let's focus on data fetching). This way, your components can focus on their main job; showing stuff to the user. So let's see what a saga will look like if we need to request a bunch of customers from an API.
import { takeLatest, call, put } from "redux-saga/effects";
// watcher saga: watches for actions dispatched to the store, starts worker saga
export function* watcherSaga() {
yield takeLatest("FETCH_CUSTOMERS", workerSaga);
}
// worker saga: makes the api call when watcher saga sees the action
function* workerSaga() {
try {
const response = yield call(fetch, "https://mydomain/api/customers");
const customers = response.data.customers;
// dispatch a success action to the store with the customers
yield put({ type: "CUSTOMERS_FETCHED", customers });
} catch (error) {
// dispatch a failure action to the store with the error
yield put({ type: "CUSTOMERS_FETCH_ERROR", error });
}
}
So what is happening here? Why is there a star after function
? This is how to define generators in ES6. The yield
statement is just as mysterious. Generators are called using the next()
function, and are paused when it encounters a yield
statement. This flow continues like this until the last yield/return/end. Redux-saga does this for us, but you can read more about it from the article about generators above. What´s important to notice here is that the execution of the saga will not continue until the promise from the API call is resolved.
The watcher saga is responsible for watching the action FETCH_CUSTOMERS
, and then call the worker saga once the action is dispatched. Then the worker saga will perform the API request. If it was successful, it dispatches the CUSTOMERS_FETCHED
action with the customers as payload, and if we got an error, we will dispatch the action CUSTOMERS_FETCH_ERROR
action with the error as payload. By defining a reducer for the actions dispatched, we can then receive the payload and i.e put the data on the redux state.
Connect your saga
Now you have your first saga - let´s look at how to connect and run your saga with your redux store. This will happen where you initialize your redux store, typically in the main entrypoint app.js
file.
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import reducer from "./reducers";
import { watcherSaga } from "./sagas";
// create the saga middleware
const sagaMiddleware = createSagaMiddleware();
// mount it on the Store
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
// then run the saga
sagaMiddleware.run(watcherSaga);
// render the application
As shown, we create the saga middleware and then put in on our store. Then we import our watcherSaga
and tells the middleware to run it. Now the watcherSaga will be able to react on the action we told it to watch.
It´s as simple as that. Happy fetching!