0.0.8 • Published 6 years ago

rest-react-redux v0.0.8

Weekly downloads
10
License
MIT
Repository
-
Last release
6 years ago

Rest React Redux

N|Solid

A higher order component for RestAPI abstraction. Reduces boilerplate and encourages clean code.

By decorating your component with queriedEntity/detailedEntity and initializing the first network call, you can have access to a managed data entity named according to the "entity name" you passed as argument.

Installation

npm:

npm install rest-react-redux --save

What you need to do:

  • Create the proper reducer structure
  • Annotate your component/container with the proper HOC
  • Enjoy the props introduced to your component and not ever worry about writing actions and reducers

This library is supposed to be used only on a restful endpoint. here is an example:

  • GET: http://www.url.com/contacts?page=1&page-size=10 returns a list of contacts and metadata about the query
  • POST: http://www.url.com/contacts creates a contact
  • GET, PUT, PATCH and DELETE: http://www.url.com/contacts/contactId gets, updates or patches the specific contact respectively

Restrictions in the current version

  • JSON endpoints: network request and response body must be of application/json content type
  • Redux-thunk: your application redux store must contain redux-thunk as middleware
import {createStore, compose, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
export const store = createStore(rootReducer, compose(applyMiddleware(thunk)));
  • Axios: your application must use axios as the network call library. BaseUrl and headers must be set in your application scope
import axios from 'axios';
axios.defaults.baseURL = 'http://www.url.com';
  • reducer: using queriedEntityReducer or detailedEntityReducer you must create a field in the root reducer matching the name of the entity you create. This is where the library manages the data
import { queriedEntityReducer, detailedEntityReducer } from 'rest-react-redux';
const reducers = {
    contacts: queriedEntityReducer('contact'),
    contact: detailedEntityReducer('contact'),
}

Higher Order Components

queriedEntity

if you intend to work with an endpoint that returns a list use queriedEntity to decorate your components:

import { queriedEntity } from 'rest-react-redux';

@queriedEntity('contact')
class ContactsPage extends React.Component {
    ...
}

NOTE: For those of you who do not enjoy decorating as much as I do, use the standard way!

queriedEntity(entityName, configoptional)

Config fieldExplanationDefault Value
resultFieldthe result field name in response body that matches the list of itemscontent
retain_numbermaximum number of queries to cache10
hideLoadIfDataFoundif set to false, will trigger loading UI even if data had been cachedtrue
reducerNameThe reducer name used for the entity. Default is the plural form of the entityName[entityName]s
preloadValidTimeThe time (milliseconds) that a preloaded query is valid and should not be re-fetched10000
smartPreloadIf set to true the library times the network calls specific to the defined entity. If the average is greater than 0.3 seconds, preloading will be cancelled. Overwrite this time with the next fieldfalse
smartThresholdTimeThe acceptable average time (milliseconds) for network calls to continue preloading in smartPreload mode300

Properties injected to the wrapped component

property (props)ExplanationExampleSample value
entityNamesThe query resultcontacts[{id: 1, name: 'John Doe'}]
entityNamesQueryParamsThe last successful parameters with which query was performedcontactsQueryParams{page: 1}
entityNamesMetadataThe metadata that is received from the endpointcontactsMetadata{totalPages: 10}
initialQueryEntityNamesA must-be-called function that initializes the query. Receives url and parameters (object)initialQueryContacts('/contacts/', {page: 1})
queryEntityNamesAny query after initial query call. It will append the new partial params on top of the previous onesqueryContacts({pageSize: 1})
createEntityNameCreates an object and performs the latest query againcreateContact({name: 'Foo Bar'})
updateEntityNameUpdates/replaces an entity. After success, will update the store and queries againupdateContact({id: 1, name: 'Foo Bar'})
patchEntityNamePatches an entity. After success, will update the store and queries againpatchContact({id: 1, name: 'Foo Bar'})
deleteEntityNameRemoves an entity. After success, will update the store and queries againdeleteContact({id: 1, name: 'Foo Bar'})
setEntityNamesPreloaderSets a function for pre-loading data for a smooth UXsetContactsPreloader(customPreloader)*
loadingEntityNamesNetwork loading statusloadingContactstrue

*Note: the preloader function will receive (partialParams, params, queryMetadata) as arguments. The function should return an array of partialParams. See the preloading section for more information

detailedEntity

if you intend to work with an endpoint that returns a detailed entity use detailedEntity to decorate your components:

import { detailedEntity } from 'rest-react-redux';

@detailedEntity('contact')
class ContactsPage extends React.Component {
    ...
}

detailedEntity(entityName, configoptional)

Config fieldExplanationDefault Value
retain_numbermaximum number of queries to cache10
hideLoadIfDataFoundif set to false, will trigger loading UI even if data had been cachedtrue
reducerNameThe reducer name used for the entity. Default is the entityName[entityName]

Properties injected to the wrapped component

property (props)ExplanationExampleSample value
entityNameThe resultcontact{id: 1, name: 'John Doe'}
initialGetEntityNamesA must-be-called function that initializes the entity. Receives url and object idinitialGetContacts('/contacts/12', 12)
getEntityNameAny get call after the initial get call. This is usually used for receiving updates if anygetContact()
updateEntityNameUpdates/replaces the entity. After success, will update the store and gets againupdateContact({id: 1, name: 'Foo Bar'})
patchEntityNamePatches the entity. After success, will update the store and gets againpatchContact({id: 1, name: 'Foo Bar'})
deleteEntityNameRemoves the entity. After success, will update the storedeleteContact({id: 1, name: 'Foo Bar'})
loadingEntityNameNetwork loading statusloadingContacttrue

Preloading

For a better user experience, you may pre-load the data that have a good chance of being loaded later. There are two main methods of preloading data:

Entity specific preloading

If you are dealing with a queried entity like calendar events, table data, chat messages etc. it might be a good idea to dynamically preload data based on what the user queries. For instance, loading the previous and the next pages of a table sounds like a good investment! To do that you simply need to set a function via property set[EntityName]sPreloader:

import { queriedEntity } from 'rest-react-redux';

@queriedEntity('contact')
class ContactsPage extends React.Component {
    
    componentDidMound() {
        this.props.initialQueryContacts('/contacts/', {page: 1, size: 20});
        this.props.setContactsPreloader((partialParams) => {
            
            const page = partialParams.page;
            
            // If the query does not contain a new page do not preload
            if (!partialParams.page) return [];
            
            // Preload previous and next pages
            return [{page: page - 1}, {page: page + 1}];
        })
    }
}

Generic preloading

You may need to preload entities that are not related to the component you are focusing at. For instance, if a user gets to the main dashboard, you want to preload all the contacts before the user goes to the contacts page. To achieve that you can use the exposed queryEntities or getEntity action generators:

queryEntities(entityName, url, params)
getEntity(entityName, url)

Usage example:

import { queryEntities, getEntity } from 'rest-react-redux';
import store from '../store';

class Dashboard extends React.Component {
    
    componentDidMound() {
        // Preload a contact list query
        store.dispatch(queryEntities('contact', '/contacts/', {page: 1}));
        
        // Preload a detailed contact
        store.dispatch(getEntity('contact', '/contacts/1'));
    }
}

Todos

  • Remove the redux-thunk dependency
  • Remove the JSON request/response requirement
  • Remove the need to update the reducer for each entity
  • Tests

License

MIT

0.0.8

6 years ago

0.0.7

6 years ago

0.0.6

6 years ago

0.0.5

6 years ago

0.0.4

6 years ago

0.0.3

6 years ago

0.0.2

6 years ago

0.0.1

6 years ago

0.0.0

6 years ago

0.1.0

6 years ago