1.0.5 • Published 6 years ago

redux-rx-http v1.0.5

Weekly downloads
11
License
MIT
Repository
github
Last release
6 years ago

redux-rx-http

TODO: Update Readme for 1.0

CircleCI

So, you like redux, you like RxJS, you're using redux-observable. You want to talk to APIs, and consume the side effects through epics, and you want a nice, simple way to do it. This library works by having a single API action where the side-effect actions (request, success, error, cancel) are passed in with the initial action in a clean, consistent way. Oh, and we have type definitions!

Important note: As of version 0.14, fetch is used internally. This means you will have to inject fetch as a dependency in your createEpicMiddleware function, e.g. isomorphic-fetch. Also, cancellation won't actually cancel the original request, it will just terminate the inner stream (so no further actions will be emitted).

Configuration

Configuration allows you to set the base URL and initial headers for all requests.

Because your base request configuration could be dynamic based on your application state, config is done as a function, with store.getState() as the primary argument.

For instance, say your authorisation token was acquired asyncronously and put in your store...

configure-store.ts

// ...imports...
import { createRxHttpEpic, RxHttpRequestBase } from 'redux-rx-http'

const rxHttpEpic = createRxHttpEpic((state: AppState): RxHttpRequestBase => ({
    baseUrl: 'https://my-excellent-api.com/v1.0',
    headers: {
        // Here we're dynamically configuring the auth token
        Authorization: getAuthToken(state),
    },
}))

const epicMiddleware = createEpicMiddleware(
    combineEpics(rootEpic, rxHttpEpic),
    // Inject our fetch dependency (at least)
    { dependencies: { fetch } },
)

const store = createStore(rootReducer, applyMiddleware(epicMiddleware))

Usage

To make a simple HTTP GET request, and then listen to the results...

actions.ts

import { rxHttpGet, createRxHttpActionTypes } from 'redux-rx-http'

const FETCH_POTATO = createRxHttpActionTypes('FETCH_POTATO')

// Action to fetch a potat from our API
export const fetchPotato = (id: string): RxHttpRequestAction =>
    rxHttpGet(`/potato/${id}`)

epics.ts

import { FETCH_POTATO } from './actions'

// Simply take the request, and map it to some sort of UI action.
const showSpinner = (action$: ActionsObservable<PotatoAction>): Observable<UIAction> =>
  action$.ofType(FETCH_POTATO.REQUEST)
    .mapTo({ type: UIActions.SHOW_SPINNER })

// Hide the spinner on done.
const showSpinner = (action$: ActionsObservable<PotatoAction>): Observable<UIAction> =>
  action$.ofType(FETCH_POTATO.FINALLY, FETCH_POTATO.CANCEL)
    .mapTo({ type: UIActions.HIDE_SPINNER })

// Consume the results of loading our potato!
const setPotato = (action$: ActionsObservable<PotatoAction>): Observable<SetPotatoAction> =>
  action$.ofType(FETCH_POTATO.SUCCESS)
    .map(action => ({ type: PotatoActions.SET_POTATO, potato: action.result }))

// Handle erroneous potato fetch
const potatoError = (action$ ActionsObservable<PotatoAction>): Observable<PotatoErrorAction> =>
  action$.ofType(FETCH_POTATO.ERROR)
    .map(action => ({ type: PotatoActions.POTATO_ERROR, error: action.error }))

## More complex usage

Of course, simply getting a potato is simple, but each function takes a third argument of a relevant thing:

  • Query params: rxHttpGet
  • Request body: rxHttpPost, rxHttpPut, rxHttpPatch
  • None: rxHttpDelete, rxHttpHead

And a final argument which is of type RxHttpRequestConfig.

export interface RxHttpRequestConfig {
  // Represents data about the request to be sent
  request?: RxHttpRequestBase

  // An arbitrary object that you can pass additional metadata in order to provide context to
  // whatever epic is consuming the side-effects, e.g. an ID, a parent ID, etc.
  args?: {}

  // Shortcut to allow easy destructuring API responses that are of the form:
  // { potato: { ... potato data ...} }
  key?: string
}

So a more complex usage could look something like this:

actions.ts

export const fetchPotatosForField = (fieldId: string,
                                     status: Status = 'ALL'): RxHttpRequestAction =>
  rxHttpGet(`/fields/${fieldId}/potatoes`, FETCH_POTATOES_FOR_FIELD, { status }, {
    key: 'potatoes',
    args: { fieldId },
  })


export const savePotato (potato: Potato): RxHttpRequestAction =>
  rxHttpPut(`/potato/${potato.id}`, SAVE_POTATO, potato, { args: { id } })

epics.ts

const potatoSavedNotification = (action$: ActionsObservable<PotatoAction>): Observable<UIAction> =>
  action$.ofType(SAVE_POTATO.SUCCESS)
    .map((action: SavePotatoAction): NotifyAction => ({
      type: UIActions.NOTIFY,
      message: `Saved potato ${action.args.id} successfully!`,
    }))

I would advise against putting callbacks in args, as that entirely misses the point.

Cancellation

Because we're using observables, requests can be cancelled!

actions.ts

// Action to cancel said fetching
export const cancelFetchPotato = () => ({ type: FETCH_POTATO.CANCEL })

Filtering output actions

If for some utterly bizarre reason the amount of actions going through the redux store is a performance issue, you can filter both global and your local actions.

Global actions

Only generate success action:

rxHttpGet('https://potato.com/api', myActions, {}, {
  request: {
    actions: [RX_HTTP_SUCCESS],
  }
})

This can be added to base request config, as with anything else

Specific actions

Only generate success action:

const myActions = createRxHttpActions('MY', ['SUCCESS'])

Simple!

1.0.5

6 years ago

1.0.4

6 years ago

1.0.3

6 years ago

1.0.2

6 years ago

1.0.1

6 years ago

1.0.0

6 years ago

0.18.1

6 years ago

0.18.0

6 years ago

0.17.2

6 years ago

0.17.1

6 years ago

0.17.0

6 years ago

0.16.0

6 years ago

0.15.4

6 years ago

0.15.3

6 years ago

0.15.2

6 years ago

0.15.1

6 years ago

0.15.0

6 years ago

0.14.2

6 years ago

0.14.1

6 years ago

0.14.0

6 years ago

0.13.0

6 years ago

0.12.0

6 years ago

0.11.3

6 years ago

0.11.2

7 years ago

0.11.1

7 years ago

0.11.0

7 years ago

0.10.0

7 years ago

0.9.0

7 years ago

0.8.5

7 years ago

0.8.4

7 years ago

0.8.3

7 years ago

0.8.2

7 years ago

0.8.1

7 years ago

0.8.0

7 years ago

0.7.7

7 years ago

0.7.6

7 years ago

0.7.5

7 years ago

0.7.3

7 years ago

0.7.2

7 years ago

0.7.1

7 years ago

0.7.0

7 years ago

0.6.2

7 years ago

0.6.1

7 years ago

0.6.0

7 years ago

0.5.2

7 years ago

0.5.1

7 years ago

0.5.0

7 years ago

0.4.0

7 years ago

0.3.2

7 years ago

0.3.1

7 years ago

0.3.0

7 years ago

0.2.1

7 years ago

0.2.0

7 years ago