Comparing version 3.0.0-3 to 3.0.0
@@ -174,7 +174,11 @@ "use strict"; | ||
value: function setStateIfNecessary(newState) { | ||
var _this$state = this.state, | ||
data = _this$state.data, | ||
status = _this$state.status, | ||
errors = _this$state.errors; | ||
var globalStateChangeComparator = _config.default.stateChangeComparator; | ||
var stateChangeComparator = localStateChangeComparator || globalStateChangeComparator; | ||
var dataIsEqual = stateChangeComparator(this.state.data, newState.data); | ||
var statusIsEqual = stateChangeComparator(this.state.status, newState.status); | ||
var errorsAreEqual = stateChangeComparator(this.state.errors, newState.errors); | ||
var dataIsEqual = stateChangeComparator(data, newState.data); | ||
var statusIsEqual = stateChangeComparator(status, newState.status); | ||
var errorsAreEqual = stateChangeComparator(errors, newState.errors); | ||
@@ -206,6 +210,6 @@ if (!dataIsEqual || !statusIsEqual || !errorsAreEqual) { | ||
value: function render() { | ||
var _this$state = this.state, | ||
data = _this$state.data, | ||
status = _this$state.status, | ||
errors = _this$state.errors; | ||
var _this$state2 = this.state, | ||
data = _this$state2.data, | ||
status = _this$state2.status, | ||
errors = _this$state2.errors; | ||
@@ -215,3 +219,5 @@ var _this$props3 = this.props, | ||
restOfProps = _objectWithoutProperties(_this$props3, ["reduxStore"]); | ||
/* eslint-disable react/jsx-props-no-spreading */ | ||
return _react.default.createElement(WrappedComponent, _extends({}, data, restOfProps, { | ||
@@ -218,0 +224,0 @@ loadStatus: status, |
@@ -22,3 +22,3 @@ "use strict"; | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } | ||
@@ -43,3 +43,3 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
var result = func(iguazuInput); | ||
return Object.assign({}, result, { | ||
return _objectSpread({}, result, { | ||
promise: result.promise.catch(function () { | ||
@@ -77,3 +77,4 @@ /* swallow */ | ||
error = result.error, | ||
data = result.data; | ||
data = result.data, | ||
isNonCritical = result.noncritical; | ||
sequencedData = _objectSpread(_defineProperty({}, prevKey, data), sequencedData); | ||
@@ -96,3 +97,5 @@ var promise = prevPromise.then(function (prevData) { | ||
}; | ||
} else if (error) { | ||
} | ||
if (error && isNonCritical !== true) { | ||
return { | ||
@@ -99,0 +102,0 @@ status: 'complete', |
{ | ||
"name": "iguazu", | ||
"version": "3.0.0-3", | ||
"version": "3.0.0", | ||
"description": "An asynchronous data flow solution for React/Redux applications", | ||
"author": "Maia Teegarden", | ||
"contributors": [ | ||
"Nickolas Oliver <nickolas.oliver@aexp.com>", | ||
"Andrew Curtis <andrew.curtis@aexp.com>", | ||
"Michael Tomcal <michael.a.tomcal@aexp.com>" | ||
"Andres Escobar <Andres.Escobar@aexp.com> (https://github.com/anescobar1991)", | ||
"James Singleton <James.Singleton1@aexp.com> https://github.com/JamesSingleton)", | ||
"Jimmy King <Jimmy.King@aexp.com> (https://github.com/10xLaCroixDrinker)", | ||
"Jonathan Adshead <Jonathan.Adshead@aexp.com> (https://github.com/JAdshead)", | ||
"Michael Tobia <Michael.M.Tobia@aexp.com> (https://github.com/Francois-Esquire)", | ||
"Michael Tomcal <Michael.A.Tomcal@aexp.com> (https://github.com/mtomcal))", | ||
"Stephanie Coates <Stephanie.Coates1@aexp.com> (https://github.com/stephaniecoates)", | ||
"Nelly Kiboi <Nelly.J.Kiboi@aexp.com> (https://github.com/nellyk)", | ||
"Nickolas Oliver <nickolas.oliver@aexp.com> (https://github.com/PixnBits)", | ||
"Andrew Curtis <andrew.curtis@aexp.com> (https://github.com/drewcur)", | ||
"Scott McIntyre <scott.mcintyre@aexp.com> (https://github.com/smackfu)" | ||
], | ||
"keywords": [ | ||
"async", | ||
"react", | ||
"redux", | ||
"react-redux", | ||
"fetch", | ||
"data", | ||
"query", | ||
"promise" | ||
], | ||
"homepage": "https://github.com/americanexpress/iguazu", | ||
"bugs": { | ||
"url": "https://github.com/americanexpress/iguazu/issues" | ||
}, | ||
"license": "Apache-2.0", | ||
@@ -19,5 +41,5 @@ "repository": { | ||
"build": "babel src --out-dir lib", | ||
"lint": "eslint --ignore-path .gitignore --ext js,jsx .", | ||
"lint": "eslint --ignore-path .gitignore --ext js,jsx,md .", | ||
"prepublish": "npm run build", | ||
"test": "jest", | ||
"test": "jest && commitlint --from origin/master --to HEAD", | ||
"posttest": "npm run lint" | ||
@@ -32,3 +54,2 @@ }, | ||
"dependencies": { | ||
"@babel/polyfill": "^7.2.5", | ||
"hoist-non-react-statics": "^3.3.0", | ||
@@ -40,10 +61,23 @@ "prop-types": "^15.5.10", | ||
"@babel/cli": "^7.2.3", | ||
"@babel/core": "^7.2.2", | ||
"@babel/core": "^7.3.3", | ||
"@babel/polyfill": "^7.2.5", | ||
"@commitlint/cli": "^8.2.0", | ||
"@commitlint/config-conventional": "^8.2.0", | ||
"@semantic-release/changelog": "^3.0.5", | ||
"@semantic-release/commit-analyzer": "^6.3.2", | ||
"@semantic-release/git": "^7.0.18", | ||
"@semantic-release/npm": "^5.3.4", | ||
"@semantic-release/release-notes-generator": "^7.3.2", | ||
"amex-jest-preset": "^5.0.0", | ||
"amex-jest-preset-react": "^5.0.1", | ||
"babel-preset-amex": "^2.0.0", | ||
"babel-jest": "^24.1.0", | ||
"babel-preset-amex": "^3.2.0", | ||
"core-js-compat": "3.4.5", | ||
"enzyme": "^3.1.1", | ||
"enzyme-to-json": "^3.2.1", | ||
"eslint": "^4.18.2", | ||
"eslint-config-amex": "^6.0.0", | ||
"eslint": "^6.5.0", | ||
"eslint-config-amex": "^11.1.0", | ||
"husky": "^3.0.9", | ||
"jest": "^24.1.0", | ||
"jest-fetch-mock": "^1.2.0", | ||
"react": "^16.0.0", | ||
@@ -54,3 +88,4 @@ "react-dom": "^16.0.0", | ||
"redux": "^4.0.4", | ||
"redux-thunk": "^2.3.0" | ||
"redux-thunk": "^2.3.0", | ||
"semantic-release": "^15.13.31" | ||
}, | ||
@@ -63,3 +98,19 @@ "peerDependencies": { | ||
"redux-thunk": "^2.3.0" | ||
}, | ||
"release": { | ||
"plugins": [ | ||
"@semantic-release/commit-analyzer", | ||
"@semantic-release/release-notes-generator", | ||
"@semantic-release/changelog", | ||
"@semantic-release/git", | ||
"@semantic-release/npm" | ||
], | ||
"branch": "master" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "npm test", | ||
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS" | ||
} | ||
} | ||
} |
387
README.md
@@ -1,9 +0,38 @@ | ||
# Iguazu | ||
<h1> | ||
<center> | ||
<br /> | ||
<img src="./iguazu.png" alt="iguazu - Iguazu" width="50%" /> | ||
<br /><br /> | ||
</center> | ||
</h1> | ||
An asynchronous data flow solution for React/Redux applications. | ||
[![npm version](https://badge.fury.io/js/iguazu.svg)](https://badge.fury.io/js/iguazu) | ||
[![Build Status](https://travis-ci.org/americanexpress/iguazu.svg?branch=master)](https://travis-ci.org/americanexpress/iguazu) | ||
> Want to get paid for your contributions to `iguazu`? | ||
> Iguazu is a simple Redux-powered Async Query engine. By using a | ||
> Higher-Order Component, Iguazu transparently manages dispatching async | ||
> requests and injecting into React props. Iguazu has an ecosystem of | ||
> adapters for various querying + caching strategies: [Iguazu RPC](https://github.com/americanexpress/iguazu-rpc), [Iguazu GraphQL](https://github.com/americanexpress/iguazu-graphql) and [Iguazu REST](https://github.com/americanexpress/iguazu-rest). | ||
## 👩💻 Hiring 👨💻 | ||
Want to get paid for your contributions to `iguazu`? | ||
> Send your resume to oneamex.careers@aexp.com | ||
## Motivation | ||
## 📖 Table of Contents | ||
* [Features](#-features) | ||
* [Usage](#-usage) | ||
* [Upgrading](#-upgrading) | ||
* [Available Scripts](#-available-scripts) | ||
* [Contributing](#-contributing) | ||
## ✨ Features | ||
* Streamlines dispatching, load states, and injecting into React props | ||
* Parallel and sequential async requests | ||
* Server-side rendering ready | ||
### Motivation | ||
[react-redux](https://github.com/reduxjs/react-redux) works great for when you want to take data that already exists in state and inject it as props into a React component, but it doesn't help you at all with the flow of loading asynchronous data into state. If a react component relies on asynchronous data you typically have to do three things: | ||
@@ -17,3 +46,12 @@ | ||
## Usage | ||
## 🤹 Usage | ||
### Installation | ||
```bash | ||
npm install --save iguazu | ||
``` | ||
### Base Concepts | ||
Iguazu exports a Higher Order Component (HOC) `connectAsync` similar to React Redux's `connect`. Instead of taking a `mapStateToProps` function, it takes a `loadDataAsProps` function. It should return a map where each key is the name of a prop that will contain some asynchronous data and the value is the load function that will load that data if it is not already loaded. The load function must synchronously return an object with the keys `data`, `status`, `error`, and `promise`. The key `data` should be the data returned from the asynchronous call. The key `status` should be either `loading` to signal the asynchronous call is in flight or `complete` to signal it has returned. The key `error` should be a truthy value if there was an error while loading. The key `promise` should be the promise of the asynchronous call. | ||
@@ -26,16 +64,43 @@ | ||
```javascript | ||
/* MyContainer.jsx */ | ||
/* actions.js */ | ||
import React from 'react'; | ||
import { connectAsync } from 'iguazu'; | ||
function MyContainer({ isLoading, loadedWithErrors, myData, myOtherData }) { | ||
export function queryMyData(param) { | ||
return (dispatch, getState) => { | ||
const data = getState().path.to.myData[param]; | ||
const status = data ? 'complete' : 'loading'; | ||
const promise = data ? Promise.resolve : dispatch(fetchMyData(param)); | ||
return { data, status, promise }; | ||
}; | ||
} | ||
export function queryMyOtherData(param) { /* Essentially the same as queryMyData */ } | ||
/* MyContainer.jsx */ | ||
function MyContainer({ | ||
isLoading, | ||
loadedWithErrors, | ||
myData, | ||
myOtherData, | ||
}) { | ||
if (isLoading()) { | ||
return <div>Loading...</div> | ||
return <div>Loading...</div>; | ||
} | ||
if (loadedWithErrors()) { | ||
return <div>Oh no! Something went wrong</div> | ||
return <div>Oh no! Something went wrong</div>; | ||
} | ||
return <div>myData = {myData} myOtherData = {myOtherData}</div> | ||
return ( | ||
<div> | ||
myData = | ||
{' '} | ||
{myData} | ||
myOtherData = | ||
{' '} | ||
{myOtherData} | ||
</div> | ||
); | ||
} | ||
@@ -47,20 +112,7 @@ | ||
myData: () => dispatch(queryMyData(ownProps.someParam)), | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)) | ||
} | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)), | ||
}; | ||
} | ||
export default connectAsync({ loadDataAsProps })(MyContainer); | ||
/* actions.js */ | ||
export function queryMyData(param) { | ||
return (dispatch, getState) => { | ||
const data = getState().path.to.myData[param]; | ||
const status = data ? 'complete' : 'loading'; | ||
const promise = data ? Promise.resolve : dispatch(fetchMyData(param)); | ||
return { data, status, promise }; | ||
} | ||
} | ||
export function queryMyOtherData(param) {/* Essentially the same as queryMyData */}; | ||
``` | ||
@@ -70,4 +122,5 @@ | ||
## Advanced Usage | ||
### SSR | ||
### Advanced Concepts | ||
#### SSR | ||
The main benefits of server side rendering are improved perceived speed and SEO. With perceived speed, the general best practice is to get something in front of the user's eyes as fast as possible. Typically that means you shouldn't wait for any data before rendering to string. For SEO, it's more important that you render the full content, and if that content is dynamic, you'll need to wait on some data. Usually not every view is important for SEO, such as logged in views, so the best option is to only preload data you absolutely have to for SEO. For this reason, Iguazu makes SSR data preloading opt in. If you would like a component's data to be loaded prior to rendering on the server, you can add a property named `ssr` with the value of true. | ||
@@ -83,3 +136,3 @@ | ||
/* Component.jsx */ | ||
function loadDataAsProps() {...} | ||
function loadDataAsProps() { /* ... */ } | ||
loadDataAsProps.ssr = true; | ||
@@ -99,4 +152,4 @@ ``` | ||
clientOnlyData: defer(() => dispatch(loadClientData())), | ||
tryToLoadOnServerData: noncritical(() => dispatch(loadIffyData())) | ||
} | ||
tryToLoadOnServerData: noncritical(() => dispatch(loadIffyData())), | ||
}; | ||
} | ||
@@ -111,3 +164,3 @@ ``` | ||
function MyComponent({ someData }) { | ||
return <ul>{someData.list.map(item => <li key={item.toString()}>{item}</li>)}</ul> | ||
return <ul>{someData.list.map((item) => <li key={item.toString()}>{item}</li>)}</ul>; | ||
} | ||
@@ -117,9 +170,8 @@ | ||
return { | ||
someData: ({ isServer }) => | ||
(isServer ? { data: { list: [] }, status: 'loading' } : dispatch(loadSomeData())) | ||
} | ||
someData: ({ isServer }) => (isServer ? { data: { list: [] }, status: 'loading' } : dispatch(loadSomeData())), | ||
}; | ||
} | ||
``` | ||
### Synchronization | ||
#### Synchronization | ||
Let's say you have a dynamic dashboard of components that are all responsible for loading their own data, but you want to wait until they are all loaded to render them so that you don't see a bunch of spinners or a partially loaded page. Since Iguazu attaches the loadDataAsProps function as a static, parent components can easily wait until their children's data is loaded before rendering them. | ||
@@ -135,3 +187,3 @@ | ||
if (isLoading()) { | ||
return <div>Loading...</div> | ||
return (<div>Loading...</div>); | ||
} | ||
@@ -143,3 +195,3 @@ | ||
<ComponentB /> | ||
<div> | ||
</div> | ||
); | ||
@@ -151,10 +203,10 @@ } | ||
ComponentA: () => iguazuReduce(ComponentA.loadDataAsProps)({ | ||
store, ownProps: { someParam: 'someParam' } | ||
store, ownProps: { someParam: 'someParam' }, | ||
}), | ||
ComponentB: () => iguazuReduce(ComponentB.loadDataAsProps)({ store, ownProps: {} }) | ||
} | ||
ComponentB: () => iguazuReduce(ComponentB.loadDataAsProps)({ store, ownProps: {} }), | ||
}; | ||
} | ||
``` | ||
### Sequencing | ||
#### Sequencing | ||
@@ -191,3 +243,3 @@ Quite often you need the results of one asynchronous call to get the inputs for another call. One way to do this is by simply using components. | ||
const ParentContainer = connectAsync({ loadDataAsProps: parentLoadDataAsProps })(Parent) | ||
const ParentContainer = connectAsync({ loadDataAsProps: parentLoadDataAsProps })(Parent); | ||
@@ -213,11 +265,19 @@ function Kids({ isLoading, kids }) { | ||
const KidsContainer = connectAsync({ loadDataAsProps: kidsLoadDataAsProps })(Kids) | ||
const KidsContainer = connectAsync({ loadDataAsProps: kidsLoadDataAsProps })(Kids); | ||
function PersonInfo({ info: { name, age } }) { | ||
return { | ||
return ( | ||
<div> | ||
<span>name: {name}</span> | ||
<span>age: {age}</span> | ||
<span> | ||
name: | ||
{' '} | ||
{name} | ||
</span> | ||
<span> | ||
age: | ||
{' '} | ||
{age} | ||
</span> | ||
</div> | ||
} | ||
); | ||
} | ||
@@ -253,11 +313,11 @@ ``` | ||
{ key: 'parent', handler: () => dispatch(loadLoggedInParent()) }, | ||
{ key: 'kids', handler: ({ parent }) => dispatch(loadKidsByParent(parent.id)) } | ||
{ key: 'kids', handler: ({ parent }) => dispatch(loadKidsByParent(parent.id)) }, | ||
]); | ||
return { | ||
...sequenceLoadFunctions | ||
...sequenceLoadFunctions, | ||
}; | ||
} | ||
const ParentContainer = connectAsync({ loadDataAsProps: parentLoadDataAsProps })(Parent) | ||
const ParentContainer = connectAsync({ loadDataAsProps: parentLoadDataAsProps })(Parent); | ||
@@ -274,8 +334,14 @@ function Kids({ kids }) { | ||
function PersonInfo({ info: { name, age } }) { | ||
return { | ||
return ( | ||
<div> | ||
<span>name: {name}</span> | ||
<span>age: {age}</span> | ||
<span> | ||
name: | ||
{name} | ||
</span> | ||
<span> | ||
age: | ||
{age} | ||
</span> | ||
</div> | ||
} | ||
); | ||
} | ||
@@ -291,3 +357,3 @@ | ||
{ key: 'second', handler: ({ first }) => dispatch(loadSecond(first.someParam)) }, | ||
{ key: 'third', handler: ({ first, second }) => dispatch(loadThird(first.someParam, second.anotherParam)) } | ||
{ key: 'third', handler: ({ first, second }) => dispatch(loadThird(first.someParam, second.anotherParam)) }, | ||
]); | ||
@@ -304,13 +370,26 @@ ``` | ||
firstA: () => dispatch(loadFirstA()), | ||
firstB: () => dispatch(loadFirstB()) | ||
})) | ||
firstB: () => dispatch(loadFirstB()), | ||
})), | ||
}, | ||
{ | ||
key: 'second', handler: ({ first: { firstA, firstB } }) => dispatch(loadSecond(firstA, firstB)) | ||
} | ||
key: 'second', handler: ({ first: { firstA, firstB } }) => dispatch(loadSecond(firstA, firstB)), | ||
}, | ||
]); | ||
``` | ||
### Updating | ||
Function handlers require the previous calls to succeed to continue to the next by default. In the | ||
event a request returns with an error all remaining calls are flagged with the same error. To bypass | ||
this default behavior, you can wrap the function handler in `noncritical` to continue without the | ||
previous results. | ||
```javascript | ||
const sequenceLoadFunctions = sequence([ | ||
{ key: 'first', handler: () => dispatch(loadFirst()) }, | ||
{ key: 'second', handler: noncritical(({ first }) => dispatch(loadSecond(first.someParam))) }, | ||
{ key: 'unrelated', handler: () => dispatch(loadUnrelated()) }, | ||
]); | ||
``` | ||
#### Updating | ||
Iguazu processes updates on Redux state changes by comparing the previous and next responses from `loadDataAsProps` using | ||
@@ -327,4 +406,4 @@ [shallowequal](https://www.npmjs.com/package/shallowequal) by default. You are able to declare a comparator function when | ||
myData: () => dispatch(queryToDeeplyNestedData(ownProps.someParam)), | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)) | ||
} | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)), | ||
}; | ||
} | ||
@@ -338,3 +417,3 @@ | ||
### Limiting | ||
#### Limiting | ||
@@ -348,4 +427,4 @@ As some functions called within `loadDataAsProps` can be expensive when ran on every Redux state change, you are able to declare a limiter function when calling `connectAsync`. Calls to `loadDataAsProps` are not limited by default. | ||
myData: () => dispatch(expensiveQueryToData(ownProps.someParam)), | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)) | ||
} | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)), | ||
}; | ||
} | ||
@@ -355,69 +434,89 @@ | ||
loadDataAsProps, | ||
stateChangeLimiter: onStateChange => debounce(onStateChange, 100), | ||
stateChangeLimiter: (onStateChange) => debounce(onStateChange, 100), | ||
})(MyContainer); | ||
``` | ||
### Fetching On User Action | ||
#### Updating and Refreshing Data | ||
In the case that `loadDataAsProps` needs to be called on a user action, the initial render can be controlled by the result of the expected user input. | ||
In the case that we need to update a remote resource and refresh stale data: | ||
```javascript | ||
/* ComponentA.jsx */ | ||
/* MyUpdatingComponent.jsx */ | ||
import React, { Component, Fragment } from 'react'; | ||
import ComponentB from './ComponentB'; | ||
import { compose } from 'redux'; | ||
import { connect } from 'react-redux'; | ||
import { connectAsync } from 'iguazu'; | ||
export default class ComponentA extends Component { | ||
import { getMyDataAction, updateMyDataAction } from './iguazuActionCreators'; | ||
class MyUpdatingComponent extends Component { | ||
constructor(props) { | ||
super(props); | ||
this.state = { buttonClicked: false }; | ||
this.state = { message: '' }; | ||
} | ||
handleClick = () => this.setState({ buttonClicked: true }); | ||
handleClick = () => { | ||
const { updateMyData, getMyData } = this.props; | ||
// Send updateMyData request | ||
const { promise: updateMyDataPromise } = updateMyData('someParam'); | ||
return updateMyDataPromise | ||
.then(() => { | ||
// Refresh getMyData to get new results | ||
const { promise: myDataPromise } = getMyData(); | ||
return myDataPromise; | ||
}) | ||
.then(() => { | ||
this.setState({ message: 'Success!' }); | ||
}); | ||
}; | ||
render() { | ||
const { buttonClicked } = this.state; | ||
const { isLoading, loadedWithErrors, myData } = this.props; | ||
const { message } = this.state; | ||
if (isLoading()) { | ||
return <div>Loading...</div>; | ||
} | ||
if (loadedWithErrors()) { | ||
return <div>Oh no! Something went wrong</div>; | ||
} | ||
return ( | ||
<Fragment> | ||
<button onClick={this.handleClick}>Click to Load</button> | ||
{ | ||
buttonClicked && <ComponentB /> | ||
} | ||
{message} | ||
<button type="button" onClick={this.handleClick}>Update</button> | ||
<h1>My Data</h1> | ||
{myData} | ||
</Fragment> | ||
) | ||
); | ||
} | ||
}; | ||
} | ||
/* ComponentB.jsx */ | ||
import React from 'react'; | ||
import { connectAsync } from 'iguazu'; | ||
// Hook up action creator functions to props to call later | ||
function mapDispatchToProps(dispatch) { | ||
return { | ||
// Update some remote resource | ||
updateMyData: (someParam) => dispatch(updateMyDataAction(someParam)), | ||
// Fetch some remote resource | ||
getMyData: () => dispatch(getMyDataAction()), | ||
}; | ||
} | ||
function ComponentB({ isLoading, loadedWithErrors, myData, myOtherData }) { | ||
if (isLoading()) { | ||
return <div>Loading...</div> | ||
} | ||
if (loadedWithErrors()) { | ||
return <div>Oh no! Something went wrong</div> | ||
} | ||
return <div>myData = {myData} myOtherData = {myOtherData}</div> | ||
}; | ||
function loadDataAsProps({ store, ownProps }) { | ||
const { dispatch, getState } = store; | ||
// Hook up data dispatches on component load | ||
function loadDataAsProps({ store }) { | ||
const { dispatch } = store; | ||
return { | ||
myData: () => { | ||
if (!ownProps.someParam) { | ||
return { status: 'complete', promise: Promise.resolve() }; // data & error should be undefined. | ||
} | ||
return dispatch(queryMyData(ownProps.someParam)); | ||
}, | ||
myOtherData: () => dispatch(queryMyOtherData(getState().someOtherParam)) | ||
} | ||
}; | ||
// Fetch some remote resource and inject it into props as myData | ||
myData: () => dispatch(getMyDataAction()), | ||
}; | ||
} | ||
export default connectAsync({ loadDataAsProps })(ComponentB); | ||
export default compose( | ||
connect(undefined, mapDispatchToProps), | ||
connectAsync({ loadDataAsProps }) | ||
)(MyUpdatingComponent); | ||
``` | ||
## Global Configuration | ||
#### Global Configuration | ||
@@ -432,6 +531,6 @@ Iguazu is also capable of consuming global configuration that will be applied to all instances of `connectAsync`. These options will be applied unless otherwise overridden by providing the equivalent setting in the `connectAsync` call. | ||
stateChangeComparator: shallowEqual, // applied globally. | ||
stateChangeLimiter: onStateChange => debounce(onStateChange, 100), // applied globally. | ||
stateChangeLimiter: (onStateChange) => debounce(onStateChange, 100), // applied globally. | ||
}); | ||
... | ||
/* ... */ | ||
@@ -441,5 +540,5 @@ function loadDataAsProps({ store, ownProps }) { | ||
return { | ||
myData: () => dispatch(queryToDeeplyNestedData(ownProps.someParam)),, | ||
myOtherData: () => dispatch(expensiveQueryToData(ownProps.someParam)) | ||
} | ||
myData: () => dispatch(queryToDeeplyNestedData(ownProps.someParam)), | ||
myOtherData: () => dispatch(expensiveQueryToData(ownProps.someParam)), | ||
}; | ||
} | ||
@@ -450,7 +549,7 @@ | ||
stateChangeComparator: deepEqual, // override global setting. | ||
stateChangeLimiter: onStateChange => debounce(onStateChange, 500), // override global setting. | ||
stateChangeLimiter: (onStateChange) => debounce(onStateChange, 500), // override global setting. | ||
})(MyContainer); | ||
``` | ||
## Known Issues | ||
#### Known Issues | ||
@@ -461,5 +560,8 @@ - Using `iguazuReduce` within a `sequence` returns the response to the next handler as an array if the data is not loaded. Pass in `promiseAsObject` to `iguazuReduce` to resolve until next major version. | ||
const sequenceLoadFunctions = sequence([ | ||
{ key: 'first', handler: () => iguazuReduce(ComponentA.loadDataAsProps, { promiseAsObject: true })({ | ||
store, ownProps: { someParam: 'someParam' } | ||
}) }, | ||
{ | ||
key: 'first', | ||
handler: () => iguazuReduce(ComponentA.loadDataAsProps, { promiseAsObject: true })({ | ||
store, ownProps: { someParam: 'someParam' }, | ||
}), | ||
}, | ||
{ key: 'second', handler: ({ first }) => dispatch(loadSecond(first.someParam)) }, | ||
@@ -469,7 +571,4 @@ ]); | ||
## Why is it called Iguazu? | ||
This library is all about helping you manage data flow from many different sources. Data flow -> water -> waterfalls -> Iguazu falls - the largest waterfalls system in the world. It could have been named something like react-redux-async, but Iguazu also expects a certain pattern, which means there could potentially be many libraries that follow this pattern that could plug in to Iguazu. A unique name will make them more discoverable. Also it sounds cool. | ||
## 🚀 Upgrading | ||
## Upgrading | ||
### v2.x.x to v3.x.x | ||
@@ -486,3 +585,37 @@ | ||
## Contributing | ||
## 📜 Available Scripts | ||
**`npm run lint`** | ||
Verifies that your code matches the American Express code style defined in | ||
[`eslint-config-amex`](https://github.com/americanexpress/eslint-config-amex). | ||
**`npm run build`** | ||
Runs `babel` to compile `src` files to transpiled JavaScript into `lib` using | ||
[`babel-preset-amex`](https://github.com/americanexpress/babel-preset-amex). | ||
**`npm test`** | ||
Runs unit tests **and** verifies the format of all commit messages on the current branch. | ||
**`npm posttest`** | ||
Runs linting on the current branch. | ||
## 🎣 Git Hooks | ||
These commands will be automatically run during normal git operations like committing code. | ||
**`pre-commit`** | ||
This hook runs `npm test` before allowing a commit to be checked in. | ||
**`commit-msg`** | ||
This hook verifies that your commit message matches the One Amex conventions. See the **commit | ||
message** section in the [contribution guidelines](./CONTRIBUTING.md). | ||
## 🏆 Contributing | ||
We welcome Your interest in the American Express Open Source Community on Github. | ||
@@ -498,8 +631,10 @@ Any Contributor to any Open Source Project managed by the American Express Open | ||
## License | ||
## 🗝️ License | ||
Any contributions made under this project will be governed by the [Apache License | ||
2.0](https://github.com/americanexpress/iguazu/blob/master/LICENSE.txt). | ||
2.0](./LICENSE.txt). | ||
## Code of Conduct | ||
## 🗣️ Code of Conduct | ||
This project adheres to the [American Express Community Guidelines](./CODE_OF_CONDUCT.md). | ||
By participating, you are expected to honor these guidelines. |
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
88808
8
12
567
0
0
613
29
- Removed@babel/polyfill@^7.2.5
- Removed@babel/polyfill@7.12.1(transitive)
- Removedcore-js@2.6.12(transitive)
- Removedregenerator-runtime@0.13.11(transitive)