gapi-redux
v5.5.0
Published
A redux client for G Adventures' API
Downloads
9
Readme
gapi-redux
A redux client for the G Adventures' API (G API). Before you can use this library you need to signup for G API
Important: as of now gapi-redux only supports a subset of resources. Look in src/schemas.js to see the list of supported resources.
React
is not a requirement for this library and can be used with any js library.
Why
You've made a redux app that connects to G API and now you have all sorts of actions like REQUEST_PLACE_DOSSIER
and REQUEST_ITINERARY
and the list keeps growing as you move forward. You're also worried about resources that you've already requested, are you fetching them again? Do you need more actions to request the child resources?
It would've been easier if you had an action creator that just did getResource('place_dossiers', 123)
.
And it would've been smart enough to handle your pagination and all the duplicate requests different components in your project make, and knew how to drill down to make separate requests for the children, right?
Well if that's you, you've come to the right place. Keep on reading.
Documentation
Getting Started
Add gapi-redux
's reducer.
your-reducers.js
import {combineReducers} from 'redux';
import {reducers as gapiReducers} from 'gapi-redux';
export default = combineReducers({
...gapiReducers
...yourReducers,
})
Add gapi-redux
's sagas to your sagas and pass in your G API key.
your-sagas.js
import {fork} from 'redux-saga/effects';
import {sagas as gapiSagas} from 'gapi-redux';
export default function *sagas() {
yield [
fork(gapiSagas, {key: YourGAPIKey}),
fork(yourSagas)
];
And finally in your code you'd introduce the reducers and sagas to redux, as you'd normally do.
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga';
import reducers from './your-reducers';
import sagas from './your-sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducers, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(sagas);
And your're all set. Any gapi-redux actions you dispatch will now be caught by the reducers and sagas.
Actions
No matter how many times or through what action a resource is requested, gapi-redux will only request that resource once. All other requests will be ignored
Using normalizer, all resources will be normalized before saving to the store.
getResource(resourceName, resourceId, { getRelated={}, force=false })
Will attempt to retrieve a single resource from the G API.
// Request a single resource
store.dispatch(getResource('places', 123)) // get place w/ id 123
// Will also make a request for the related country to this resource
store.dispatch(getResource('places', 123, { getRelated:{country: null } }))
// After requesting the place associated with each place dossier,
// will then attempt to make a request for each country associated with each place
store.dispatch(getResource('place_dossiers', 123, { getRelated:{place: {country: null }} }))
Arguments
| Parameter | Description |
| --- | --- |
| resourceName
: String [required]
| The name of the resource to request |
| resourceId
: Number [required]
| The id of the resource to request |
| Options: Object
| getRelated
: Object
: By default when requesting a resource, any child resources present will be marked as stubs. getRelated
is used to make deep requests for those child stubs. The getRelated
object can include multiple keys. getResource
will attempt to request all the given resources after the main resource has been retrieved. The value of each key can either be null
or another object containing more resource keys. See examples section below. |
| | force
: Boolean [default=false]
: By default gapi-redux will deny requesting a resource that has already been loaded to the store. Passing true
will force getResource
to make a request whether it exists or not. |
Examples
store.dispatch(getResource('dossiers', 10, { getRelated:{accommodation_dossiers: {primary_country: null, location: {country: null}}} }))
The above action will result the following state: Notice that we only made a request for dossier 10, but getRelated
is making additional requests to all related resources for the current dossier and after normalizing the data, saves them to the store. Also, since features
was not included in getRelated
, it is marked as stub. For sub-resources nested inside a non-resource object you can pass a string to the actual sub-resource
// getRelated will try to pass through the address and make a separate request for the city (it's `places` resource)
store.dispatch(getResource('accommodation_dossiers', 10, {'address.city': null}); // `address` is not a resource, but `city` is
listResource(resource, paginationKey, { page=1, query={}, getRelated={}, pageSize=20 })
Will retrieve one page of the resource and save it in the store under the given paginationKey.
// Request the first page
store.dispatch(listResource('countries', 'myCountries'))
// After retrieving the list of places, will request each place's related country and save it in the store.
store.dispatch(listResource('places', 'listOfPlaces'), { page:1, query:{}, getRelated:{country: null} })
Arguments
| Parameter | Description |
| --- | --- |
| resource
: String [required]
| The name of the resource to request |
| paginationKey
: String [required]
| A resource should be allowed to have multiple paginations. Lets say you have a form field component with search and filtering capabilities. One form might decide to use that component for more than one of it's fields. The search results (pagination) for each component, shouldn't affect the results in the other filed component. More importantly, neither should affect the list view page for that resource. Passing a paginationKey
will allow the same resource to have multiple paginations. |
| Options: Object
| page
: Number [default=1]
: Page number to request |
| | query
: Object
: Any query to pass to the G API |
| | getRelated
: Object
: Request related resources for each item in the list after the retrieving the main resource |
allResource(resource, { query:{}, getRelated:{}, getStubs:true })
Request every item in a resource. allResource
will make separate calls to each page of a resource. Depending on the resource, this can sometimes take long time to complete. Use with caution.
Arguments
| Parameter | Description |
| --- | --- |
| resource
: String [required]
| The name of the resource |
| Options: Object
| query
: Object
: Any query to pass to the G API |
| | getRelated
: Object
: Request related resources for each item in the list after the retrieving the main resource |
| | getStubs
: Boolean [default=true]
: In most cases when using allResource
, collecting only the stubs would suffice. Passing false
can help speedup the request process. This value is true
by default. |
createResource(resource, { data={}, resolve, reject })
Creates a single resource.
You can wrap createResource
in a promise. It'll call resolve
and reject
accordingly
const p = new Promise( (resolve, reject) => {
store.dispatch( createResource('place_dossier', {some data...}, resolve, reject);
}).then(
( response => { /* do something with the response */ },
( error => { /* do something with the error */ }
)
updateResource(resource, id, { data={}, resolve, reject })
Updates a single resource.
You can wrap updateResource
in a promise. It'll call resolve
and reject
accordingly
clearPagination(resource [, paginationKey])
Clears the pagination data from the store of a given resource.
Arguments
| Parameter | Description |
| --- | --- |
| resource
: String [required]
| The name of the resource |
| paginationKey
: String
| Will only clear pagination data under a given paginationKey
. |
Data Availability
When dispatching getResource()
, listResource()
, and allResource()
the actual resource and it's children will be added to the store through multiple requests. In each request, part of the data will be written to the store
This allows for gradually displaying results, to the user, as they load. While the user is presented with the base data, related resources will be slowly loaded into the page.
getResource()
- Request the actual resource
- Any child resources marked by schemas will be normalized and moved to their own resource branch in the store
- If
getRelated
made a request for a child resource, a separategetResource()
call is made for the child. Repeat step 1 for child resources.
listResource()
- Make a request for the list endpoint of a resource. The returned result will hold pagination information along with a list of stubs.
- Write the pagination information
- Write the list of stubs to the store in one go.
- Dispatch
getResource()
for each stub in the list. Repeat all steps ofgetRelated
for each resource.
allResource()
- Make a request for the first page of a resource. The page size is set to 50.
- Write stubs to the store in one go.
- Dispatch
getRelated()
for each stub in the list. Repeat all steps ofgetRelated
for each resource. - Make a request for the next page if available.
Selectors
selectItem(state, resource, id)
Selects a single item from the store. Will return null
if not found.
Important Using normalizr, all requests to G API will be normalized before saving to the store.
selectItem
will return a denormalized version of your resource.
// requesting the place and place.country in `getRelated`
store.dispatch(getResource('place_dossiers', 123, {place: {country: null}}))
//...
const state = store.getState();
const dossier = selectItem(state, 'place_dossiers', 123);
// Selectors will return denormalized data.
console.log(dossier.place.country.name) // The name of the country
console.log(dossier.segment.name) // Null.
// `segment` was never requested by either `getRelated` or any other action. It's just stub data.
selectPage(state, resource, paginationKey, page)
Selects all resource items in one page and returns a denormalized version of each. Returns an array of items.
selectCurrentPage(state, resource, paginationKey)
Selects all resource items in the current page. Technically this function should be used within your components, instead of selectPage
. The pagination already knows which page to display. Returns an array of items.
selectAll(state, resource, orderKey=null, raw=false)
Selects all items currently available in the store. This does not necessarily reflect all items in G API, only what the action have loaded into the store. Returns an array of items if raw=false
.
orderKey
: Order the resource based on one of it's keysraw
: iftrue
, the selector will not attempt to convert the data to an array, and will return the exact format saved in the store.
selectAllPages(state, resource, paginationKey, raw=false)
Like selectALl
will return all loaded resources, but under a certain paginationKey
. Possible use cases are for pages where each page of data loads after scrolling to the bottom or by clicking on a "Load More" button.
selectPagination(state, resource, paginationKey)
Selects the pagination info of a resource/paginationKey combination.
store.dispatch(listResource('place_dossiers', 'aKey', 1, {}, {}, 3); // Request page 1, with a page size of 3
store.dispatch(listResource('place_dossiers', 'aKey', 2, {}, {}, 3); // Request the 2nd page.
...
const state = store.getState();
const pagination = selectPagination(state, 'place_dosiers', 'aKey');
console.log(pagination)
...
{
fetching: false
totalCount: 1000 // The number of total items this resource has
currentPage: 2 // The second page was the last requested page by the actions
pageSize: 3
pages: {
1: [ 1, 2, 3 ], // resource ids of items in page 1
2: [ 4, 5, 6 ] // resource ids of items in page 2
all: [ 1, 2, 3, 4, 5, 6 ], // all ids currently loaded by the store
}
}