[018.2] Using and Understanding Sagas

API requests using Redux Sagas

Subscribe now

Using and Understanding Sagas [01.05.2018]

In today's episode we will see how to start a simple app with ready components and start doing some API calls to an API.

We will use Ignite, the React Native CLI/Boilerplate, which we already know from past episodes. Together with Ignite, we will be using Native Base boilerplate code. We’ve also seen Native Base in past episodes. It gives us some components to be used in our application.

We already have the Ignite CLI installed. We just need to create a new Ignite application and pass the boilerplate parameter as native-base-boilerplate. It takes a couple seconds to install and during the installation it asks some standard libraries for internationalization, icons, similar to what Ignite does when we create a new app.

Now, let's run our App and see what it looks like. We have a react native app with a login screen, and ops. It seems there is something wrong with the drawer. It seems this is a bug on React Navigation and to solve this we need to pass, some attributes to our DrawerNavigator. Now, it works.

Our app has two list screens. A simple list screen and a card list screen.

Understanding how the API calls work with Sagas

We already have an Api Service ready. It calls the GitHub API by passing a username and it gets the avatar. Actually, the GitHub API returns all the user information, but we are only getting the avatar.

We are calling the API inside our Saga. We saw Redux Sagas in past episodes. They help make async requests and handle actions that have side effects. In our case, we have a Saga called getUserAvatar. So, it gets the response from the api get user call by passing the username.

If the response is ok, we set the avatar in our state by using the action userSuccess. Otherwise, we will call an action for failure.

export function * getUserAvatar (api, action) {
  const { username } = action
  // make the call to the api
  const response = yield call(api.getUser, username)

  if (response.ok) {
    const firstUser = path(['data', 'items'], response)[0]
    const avatar = firstUser.avatar_url
    // do data conversion here if needed
    yield put(GithubActions.userSuccess(avatar))
  } else {
    yield put(GithubActions.userFailure())
  }
}

These actions are inside the file GithubRedux. Here we have our action creators, in this case: userRequest, userSuccess and userFailure. We have our reducers, which are functions. We call each reducer function based on the Action Type. So, if we dispatch an action whose the type is USER_REQUEST, it will match the reducer function request from the GithubRedux.

export const reducer = createReducer(INITIAL_STATE, {
  [Types.USER_REQUEST]: request,
  [Types.USER_SUCCESS]: success,
  [Types.USER_FAILURE]: failure
});

This is the way Redux Sauce works.

To start all the Sagas actions, we have a root saga that receives all the Sagas we have.

export default function * root () {
  yield all([
    // some sagas only receive an action
    takeLatest(StartupTypes.STARTUP, startup),
    takeLatest(LoginTypes.LOGIN_REQUEST, login),

    // some sagas receive extra parameters in addition to an action
    takeLatest(GithubTypes.USER_REQUEST, getUserAvatar, api)
  ])
}

Make an API call

Now, we will make an API request by passing a username from GitHub, and we will return its avatar. To do this we will create a screen called ApiScreen, and we will copy this screen from another screen we have.

We want this screen in the Drawer menu, so we just need to add this as a key in the DrawerNavigator.

We will have redux connected with this component. We need to connect it at the end and pass the two functions mapDispatchToProps and mapStateToProps.

The function called getUserAvatar receives a username. The Action type will be USER_REQUEST, the same name we have had before in our redux.

As for our state, since we have a reducer for github, the state will be state.github. In our state from the github reducer we have avatar. So, this will be state.github.avatar.

const mapStateToProps = state => {
  return {
    avatar: state.github.avatar
  };
};

const mapDispatchToProps = dispatch => {
  return {
    getUserAvatar: username =>
      dispatch({ type: "USER_REQUEST", username: username })
  };
};
export default connect(mapStateToProps, mapDispatchToProps)(ApiScreen);

We will create a button to get the avatar image from Josh.

  <Button onPress={() => this.props.getUserAvatar("knewter")}>
    <Text> Get knewter Avatar </Text>
  </Button>

Debugging

I will put a debugger inside our mapStateToProps function, just to see what our state looks like in each request.

const mapStateToProps = state => {
  debugger;
  return {
    avatar: state.github.avatar
  };
};

Initial State

In the first request, we can see what our state looks like. Each key of the state is a reducer. We already have something in the state for the avatar and username.

This initial data is done by the StartupSagas we have. So, basically it makes an API request once we start the app.

Making the Request

Now, making our request, we can see the username is knewter, the fetching is true and the avatar is null. This is the moment where the API request is being made. We could use the fetching attribute to, for example, to do a gif loader in our screen.

Once the API is done, we can see it gets the correct avatar URL.

We need to add an Image component in our screen. The URL source of the image will be the avatar coming from the state.

If we check the debugger now, we will see everything in our state is correct. We got an error but, if we dismiss it, we can see the image.

This error is because when we are doing the request, the fetching is true, but the avatar url is null. Then it tries to show an image null. What I will do is just change the avatar image to a loading image or something similar. I will look for one on the internet and use its URL. So, once it's fetching the loading image will be shown.

Final Result

Once we see the screen for the first time, we can see the first image generated by the Startup Sagas. Then we do our request and while the request is being made we can see the loading image we have added. Finally we have Josh's image.

What we will see next

In the next episodes, we will see how to manage the state when we don't have internet, and how to create more actions for the reducer.

Resources