0.2.0 • Published 5 years ago
redux-offline-wizard v0.2.0
Redux Offline Wizard
Provides fllowing features for redux apps :
- Persistent redux store with support for store migrations out of the box : redux store will be saved in browser storage using localforage
- Guaranteed scheduled or delayed XHR requests : setup requests to be sent for example after two days, smoe seconds, when user reopens app, etc
- Auto-Retry for XHR requests: setup requests to be resent automatically. You can add maximum retry and expire timestamp to control retries
- Support for optimistic UI: feel free to use optimism in your reducers, that means you can for example delete a post in UI as soon as user clicks trash button and call
addToQueue
(with retry option) to add delete request to queue.redux-offline-wizard
will resend request (when goes online) until receive success response from server
Demo
A very usefull demo to play and see all redux-offlie-wizard
does step by step and in action.
You can dispatch any action and see how store is persisted, requests are retried, scheduled requests are sent on time and more
Quick Start
1. Install the package
npm install --save redux-offline-wizard
2. Add reduxOfflineWizard
branch in root of state tree
import { combineReducers } from "redux";
import { reduxOfflineWizard } from "redux-offline-wizard";
// ...
export default combineReducers({
reduxOfflineWizard,
// other branches ...
});
3. Use createOfflineStore
to create store
import createOfflineStore from "redux-offline-wizard";
//...
export default createOfflineStore(
reducers, // root reducer
middleware, // middleware = applyMiddleware( ... );
);
4. Pass renderer function to offline store creator
import React from "react";
import { render } from "react-dom";
import { Provider } from "react-redux";
//...
// createStore is the exported function in previous step
import createStore from "./store";
// App Layout
import Layout from "./Layout";
// put render in a function and pass it to
// store creator, this function will receive
// the store as first argument
function renderApp(store) {
render(
<Provider store={store}>
<Layout />
</Provider>,
document.getElementById("root")
);
}
// second argument is an int indicating the store version
createStore(renderApp, 0);
5. Use addToQueue
for XHR requests in actions
Now you can use addToQueue
function to add XHR requests to outgoing or scheduled queue
More examples for addToQueue
Full addToQueue
API
import { addToQueue } from "redux-offline-wizard";
// ...
// addToQueue(action, request, config)
export const someAction = () => addToQueue(
{
type: [ FETCH_GITHUB ],
payload: {},
},
{
url: "https://github.com",
key: "https://github.com",
body: {},
method: "GET",
},
{
retry: true,
maxRetry: 3,
}
)
6. Use fetch postfixes in reducers
To catch actions in reducers, you should use data fetch postfixes appended to action names
import {
DATA_FETCH_REJECTED,
DATA_FETCH_PENDING,
DATA_FETCH_FULFILLED,
} from "redux-offline-wizard";
import { FETCH_DATA } from "./actionNames"
const initialState = {
pending: false,
rejected: false,
fulfilled: false,
data: {}
};
export default (
state = initialState,
action
) => {
switch (action.type) {
case `${FETCH_DATA}${DATA_FETCH_PENDING}`: {
return {
...state,
pending: true,
rejected: false,
}
}
case `${FETCH_DATA}${DATA_FETCH_FULFILLED}`: {
return {
...state,
pending: false,
rejected: false,
fulfilled: true,
}
}
case `${FETCH_DATA}${DATA_FETCH_REJECTED}`: {
return {
...state,
pending: false,
rejected: true,
}
}
default: {
return state;
}
}
}
Examples
Request with auto-retry
import { addToQueue } from "redux-offline-wizard";
import { FETCH_USER } from "./actionNames";
// ...
// addToQueue(action, request, config)
export const someAction = () => addToQueue(
{
// action names to dispatch
type: [ FETCH_USER ],
// this payload will become meta (action.meta)
// in reducer, request's response will be main paylaod
payload: {},
},
{
url: "https://user.fetch.api.url",
key: "https://user.fetch.api.url",
body: {},
headers: {
Authorization: `Bearer ${token}`,
//...
},
method: "GET",
},
{
// if false, request will not be resent
retry: true,
// request will be sent 4 times maximum
maxRetry: 3,
// request have 60 seconds maximum time to get succeeded,
// otherwise will be considered failed and removed from queue
expire: 60,
}
)
Delayed or scheduled requests
import { addToQueue } from "redux-offline-wizard";
import { REMIND_USER } from "./actionNames";
// ...
// addToQueue(action, request, config)
export const someAction = () => addToQueue(
{
// action names to dispatch
type: [ REMIND_USER ],
// this payload will become meta (action.meta)
// in reducer, request's response will be main paylaod
payload: {},
},
{
url: "https://remind.user.api.url",
key: "https://remind.user.api.url",
body: {
reminder: 'e.g. Send Email to notify order expiration',
//...
},
headers: {
Authorization: `Bearer ${token}`,
//...
},
method: "POST",
},
{
delay: {
// if true, request will be added to outgoing queue on app start
onEnter: false,
// request will be added to outgoing queue after at least one week
timeout: 60 * 60 * 24 * 7,
}
}
)
Full API
addToQueue(action, request , config)
addToQueue (
{
type: [
ACTION_NAME_1,
ACTION_NAME_2,
],
payload: {},
},
{
url: "https://...",
key: "some unique key",
body: {},
headers: {},
method: "GET, POST, PUT, ...",
},
{
retry: true,
maxRetry: 3,
expire: 60,
delay: {
onEnter: true,
timeout: 60 * 60 * 24 * 7,
},
dataFetchPostfixes: {
pending: 'pending action name postfix',
rejected: 'rejected action name postfix',
fulfilled: 'fulfilled action name postfix'
}
}
)
- action
type: an array including all action types to dispatch. All types will go through 2 out of these 3 states:
pending
,rejected
,fulfilled
. Notice: all action types supplied for type will get a postfix appended to end of them for any state. for example if action name isFETCH_USER
, it will dispatchFETCH_DATA_PENDING
when pending andFETCH_DATA_FULFILLED
when fulfilled. Postfixes can be imported using named imports (see reducer example) or can be configured using config argument payload: action payload to be passed to reducer. Notice: reducers will receive this payload inmeta
field and instead will receive response of request inpayload
field of action - request url: request target key: a unique key for request Notice: avoid using a UUIDs for requests. a good practice is to use url as key but consider this way only one request with a url will exist in queue at a time and if you add any request with same url, will override existing one. However this is useful in many cases but if some requests need to have several instances, you can append some ID to url for use as key method: request method body : Request body. Can be any value valid for axios * headers (optional): Request headers
- config (optional)
retry: Default is false. if true, request will be retried until reach one of three conditions:
request succeeds
max retry reaches
expiration timeout passes
maxRetry: Maximum times to resend request. default is 5. for example a request with
maxRetry: 2
will get maximum 3 tries expire: Expiration timeout in seconds. If this field was present and timeout reached, request will be dropped from queue and will not be retried delay onEnter: if true, Request will be added to outgoing queue when user visits app. default is false timeout: Time in seconds to wait before sending request. If present and timeout reached, request will be added to outgoing queue. For example if you want a request to be sent after at least one week from now, you can set `timeout: 3600 24 7`. Consider if onEnter is true, request will be sent on app start regardless of timeout dataFetchPostfixes: Postfixes to be appended to action types on any of 3 states. You can access default postfixes with named imports :
import {
DATA_FETCH_REJECTED,
DATA_FETCH_PENDING,
DATA_FETCH_FULFILLED,
} from "redux-offline-wizard";
Contributing
Contributions of any scale are welcome
License
MIT