Taming network with redux-requests, part 2 - Basic usage
Tags: javascript, redux, react, webdev
Published: 2020-07-21 23:37:40

In the [previous part of this series](https://dev.to/klis87/taming-network-with-redux-requests-part-1-introduction-1335) we mentioned many issues connected with making AJAX requests and how Redux could help us with them. We also introduced [redux-requests library](https://redux-requests.klisiczynski.com/). 

Now we will take those issues one by one and see how they are solved in `redux-requests`. Before we could do that though, we need to learn how to use this library.

## Initial setup

Before you start, we need to install required dependencies first:
```bash
npm install axios @redux-requests/core @redux-requests/axios
```
As you noticed, we will use `axios` to make AJAX requests, but this library supports `Fetch API` and others too, so note you are not forced to use `axios` by any means.

Also, probably you already have those, but just in case make sure you have below installed too:
```bash
npm install redux reselect
```

Now, to start using `redux-requests`, you need to add below code in a place you initialize Redux store, something like:
```js
import axios from 'axios';
import { handleRequests } from '@redux-requests/core';
import { createDriver } from '@redux-requests/axios';

const configureStore = () => {
  const { requestsReducer, requestsMiddleware } = handleRequests({
    driver: createDriver(axios),
  });

  const reducers = combineReducers({
    requests: requestsReducer,
  });

  const store = createStore(
    reducers,
    applyMiddleware(...requestsMiddleware),
  );

  return store;
};
```

So, as you can see, all you need to do is call `handleRequests` function with a driver of your choice and use the returned reducer and middleware in `createStore`.

## Queries

After initial setup is done, you will gain a power to send AJAX requests with just Redux actions!

For example, imagine you have and endpoint `/books`. With pure `axios`, you could make a request as:
```js
axios.get('/books').then(response => response.data);
```

With `redux-requests` all you need to do is write a Redux action and dispatch it:
```js
const fetchBooks = () => ({
  type: 'FETCH_BOOKS',
  request: {
    url: '/books',
    // you can put here other Axios config attributes, like data, headers etc.
  },
});

// somewhere in your application
store.dispatch(fetchBooks());
```

`fetchBooks` is just a Redux action with `request` object. This object is actually a config object passed to a driver of your choice - in our case `axios`. From now on let's call such actions as **request actions**.

So, what will happen after such an action is dispatched? The AJAX request will be made and depending on the outcome, either `FETCH_BOOKS_SUCCESS`, `FETCH_BOOKS_ERROR` or `FETCH_BOOKS_ABORT` action will be dispatched automatically and data, error and loading state will be saved in the reducer.

To read response, you can wait until request action promise is resolved:
```js
store.dispatch(fetchBooks()).then(({ data, error, isAborted, action }) => {
  // do sth with response
});
```
... or with `await` syntax:
```js
const { data, error, isAborted, action } = await store.dispatch(fetchBooks());
```

However, usually you would prefer to read this state just from Redux store. For that you can use built-in selectors:
```js
import { getQuery } from '@redux-requests/core';

const { data, error, loading } = getQuery(state, { type: FETCH_BOOKS });
```

What is **query** by the way? This is just a naming convention used by this library, actually borrowed from _GraphQL_. There are two sorts of requests - **queries** and **mutations**. **Queries** are made just to fetch data and they don't cause side-effects. This is in contrast to **mutations** which cause side-effects, like data update, user registration, email sending and so on. By default requests with `GET` method are queries and others like `POST`, `PUT`, `PATCH`, `DELETE` are mutations, but this also depends on drivers and can be configured.

## Mutations

What about updating data? Let's say you could update a book with `axios` like that:
```js
axios.post('/books/1', { title: 'New title' });
```
which would update `title` of book with `id: 1` to `new title`.

Again, let's implement it as Redux action:
```js
const updateBook = (id, title) => ({
  type: 'UPDATE_BOOK',
  request: {
    url: `/books/${id}`,
    method: 'post',
    data: { title },
  },
  meta: {
    mutations: {
      FETCH_BOOKS: (data, mutationData) =>
        data.map(book => book.id === id ? mutationData : book),
    }
  },
});

// somewhere in your application
store.dispatch(updateBook('1', 'New title'));
```

There are several interesting things here. First of all, notice `post` method, so this request action is actually a **mutation**. Also, look at `meta` object. Actually request actions can have not only `request` object, but also `meta`. The convention is that `request` object is related to a driver, while `meta` allows you to pass driver agnostic options, all of which will be described later. Here we use `mutations`, which in this case is used to update data of `FETCH_BOOKS` query. The first argument is `data` (current `data` of `FETCH_BOOKS` query) and `mutationData` (data returned from server for `UPDATE_BOOK` mutation).

And how to read responses and mutation state? Similar to queries:
```js
store.dispatch(updateBook('1', 'New title')).then(({ data, error, isAborted, action }) => {
  // do sth with response
});
```
... or with `await` syntax:
```js
const { data, error, isAborted, action } = await store.dispatch(updateBook('1', 'New title'));
```
... or just by using selector:
```js
import { getMutation } from '@redux-requests/core';

const { error, loading } = getMutation(state, { type: UPDATE_BOOK });
```

Notice no `data` in `getMutation` - this is because mutations are made to cause side-effects, like data update. We don't store `data` in reducers for mutations,
we do this only for queries.

## Request actions philosophy

Notice, that usually you would do such a thing like data update with a reducer. But this library has a different approach, it manages the whole remote state with one global reducer (`requestsReducer`) and advocates having update instructions in requests actions themselves. This has the following advantages:
- you don't need to write reducers, just actions
- all logic related to a request is kept in one place, encapsulated in a single action
- because there is one global reducer, remote state is standardized which allowed to implement many features like caching, automatic normalisation and so on
- as a consequence of above, you also don't need to write selectors, they are provided for you

A theoretical disadvantage is that passing a function like update function to an action makes it not serializable. But in reality this is not a problem, only reducers have to be serializable, actions not, for example time travel will still work.

Of course you still could listen to request actions in your reducers, but it is recommended to do this only for an additional state, so you would not duplicate state stored in `requestsReducer`, which is never a good thing.

## What's next?

In [the next part](https://dev.to/klis87/taming-network-with-redux-requests-part-3-race-conditions-and-requests-aborts-3984) of this series, we will discuss race conditions and importance of requests aborts.