Skip to content

Commit

Permalink
fixes #18
Browse files Browse the repository at this point in the history
capaj committed Mar 2, 2018
1 parent fd4ea59 commit 659d091
Showing 7 changed files with 8,998 additions and 3,461 deletions.
70 changes: 50 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
# react-promise
# react-promise

[![NPM badge](https://nodei.co/npm/react-promise.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/react-promise/)

a react.js component for general promise - no need for stateful component just to render a value hidden behind a promise or for a simple form.
Let's consider a trivial example: you have a promise such as this

```javascript
let prom = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('a value')
}, 100)
let prom = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('a value')
}, 100)
})
```

and you want to make a component, which renders out in it's body 'a value'. Without react-async, such component looks like this:

```javascript
class ExampleWithoutAsync extends React.Component { // you can't use stateless component because you need a state
constructor () {
@@ -28,46 +31,73 @@ class ExampleWithoutAsync extends React.Component { // you can't use stateless c
```
and with react-async:
```javascript
import Async from 'react-promise'

const ExampleWithAsync = (props) => <Async promise={prom} then={(val) => <div>{val}</div>}/>
const ExampleWithAsync = props => (
<Async promise={prom} then={val => <div>{val}</div>} />
)
```
Much simpler, right?
Because the latter code handles that promise declaratively instead of imperatively.
In case you need user input before you can make the async call, there is a `before` property. Assign a function into it if you need to render a form for example.
```javascript
<Async before={(handlePromise) => {
return <form>
<input></input>
<button onClick={() => {
handlePromise(Promise.resolve('awesome data'))
}}>do something async like a POST request</button>
</form>
}}
<Async
before={handlePromise => {
return (
<form>
<input />
<button
onClick={() => {
handlePromise(Promise.resolve('awesome data'))
}}
>
do something async like a POST request
</button>
</form>
)
}}
/>
```
The form is rendered before the promise is resolved. If you ever need to reset the Async to `before` after promise has resolved/rejected get the Async ref and use
```javascript
ref.setState({status: 'none'})
ref.setState({ status: 'none' })
```
## install
With npm:
```
npm i react-promise
```
## defaultPending
You can define a single pending state for all instances of `<Async />` by defining a `defaultPending` property on the `Async` component class. Full example here:
```javascript
import Async from '../src/react-promise'

Async.defaultPending = (
<h1>my uber loading spinner/text/whatever used for all</h1>
)
```
## [Available props](https://github.com/capaj/react-promise/blob/master/src/react-promise.js#L60):
All props are optional
- **promise** a promise you want to wait for
- **before** if no promise is provided, Async will invoke this inside it's render method-use for forms and such
- **then** runs when promise is resolved. Async will run function provided in it's render passing a resolved value as first parameter.
- **catch** runs when promise is rejected. Async will run function provided in it's render passing an error as first parameter.
- **pending** is a React node which will be outputted from Async render method while promise is pending. If none is provided, defaults to `<div/>`
* **promise** a promise you want to wait for
* **before** if no promise is provided, Async will invoke this inside it's render method-use for forms and such
* **then** runs when promise is resolved. Async will run function provided in it's render passing a resolved value as first parameter.
* **catch** runs when promise is rejected. Async will run function provided in it's render passing an error as first parameter.
* **pending** can be a React node which will be outputted from Async render method while promise is pending. Can be a function too. If none is provided and `defaultPending` is set, then outputs the default.
## To use with Typescript
12,287 changes: 8,866 additions & 3,421 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 25 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"name": "react-promise",
"version": "2.0.2",
"description":
"a react.js component for declarative way of handling promises",
"description": "a react.js component for declarative way of handling promises",
"main": "./dist/react-promise.cjs.js",
"types": "./src/async.d.ts",
"module": "./dist/react-promise.es.js",
@@ -22,8 +21,16 @@
"type": "git",
"url": "git+https://github.com/capaj/react-async.git"
},
"files": ["dist", "src"],
"keywords": ["react", "promise", "q", "declarative"],
"files": [
"dist",
"src"
],
"keywords": [
"react",
"promise",
"q",
"declarative"
],
"author": "capajj@gmail.com",
"license": "MIT",
"bugs": {
@@ -36,7 +43,7 @@
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-loader": "^7.1.3",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-register": "^6.26.0",
@@ -45,24 +52,29 @@
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"husky": "^0.14.3",
"jest": "^22.1.4",
"jest": "^22.4.2",
"raf": "^3.4.0",
"react": "^16.2.0",
"react-addons-test-utils": "^15.6.2",
"react-dom": "^16.2.0",
"react-test-renderer": "^16.2.0",
"rimraf": "^2.6.2",
"rollup": "^0.55.0",
"rollup": "^0.56.3",
"rollup-plugin-babel": "^3.0.3",
"standard": "^10.0.3",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.11.1"
"standard": "^11.0.0",
"webpack": "^4.0.1",
"webpack-dev-server": "^3.1.0"
},
"dependencies": {
"prop-types": "^15.6.0"
"prop-types": "^15.6.1",
"webpack-cli": "^2.0.10"
},
"jest": {
"setupFiles": ["raf/polyfill"],
"roots": ["src"]
"setupFiles": [
"raf/polyfill"
],
"roots": [
"src"
]
}
}
33 changes: 32 additions & 1 deletion showcase/index.js
Original file line number Diff line number Diff line change
@@ -3,10 +3,41 @@ import ReactDOM from 'react-dom'
import React from 'react'
import Async from '../src/react-promise'

Async.defaultPending = <h1>looooad</h1>

const AsyncShowcase = () => (
<div>
<h2>With default pending state</h2>
<Async
then={response => {
return (
<div>
got back:
{response}
</div>
)
}}
before={handlePromise => {
return (
<div>
<button
onClick={() => {
handlePromise(
fetch('https://www.reddit.com/r/javascript.json').then(res =>
res.text()
)
)
}}
>
fetch
</button>
</div>
)
}}
/>
<h2>With custom pending state</h2>
<Async
pending={'loading'}
pending={'Loading'}
then={response => {
return (
<div>
6 changes: 4 additions & 2 deletions src/async.d.ts
Original file line number Diff line number Diff line change
@@ -5,13 +5,15 @@ export interface Props<T> {
before?: (handlePromise: () => void) => React.ReactNode
then?: (value: T) => React.ReactNode
catch?: (err: any) => React.ReactNode
pending?: React.ReactNode
pending?: () => React.ReactNode | React.ReactNode
}

export interface State {
status: 'none' | 'pending' | 'resolved' | 'rejected'
}

declare class Async<T> extends React.Component<Props<T>, State> {}
declare class Async<T> extends React.Component<Props<T>, State> {
defaultPending: () => React.ReactNode | React.ReactNode
}

export default Async
18 changes: 14 additions & 4 deletions src/react-promise.js
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ class Async extends React.Component {
prom.then(
res => {
if (this.promise !== prom) {
return // this promise has been switched for some other before it resolved, so we can ignore this
return // this promise has been switched for some other before it resolved, so we can early return
}
if (!this.unmounted) {
this.setState({
@@ -42,7 +42,7 @@ class Async extends React.Component {
},
err => {
if (this.promise !== prom) {
return // this promise has been switched for some other before it rejected, so we can ignore this
return // this promise has been switched for some other before it rejected, so we can early return
}
if (!this.unmounted) {
this.setState({
@@ -73,7 +73,17 @@ class Async extends React.Component {
break
case statusTypes.pending:
if (props.pending) {
return props.pending
if (typeof props.pending === 'function') {
return props.pending()
} else {
return props.pending
}
} else if (Async.defaultPending) {
if (typeof Async.defaultPending === 'function') {
return Async.defaultPending()
} else {
return Async.defaultPending
}
}
break
case statusTypes.resolved:
@@ -96,7 +106,7 @@ Async.propTypes = {
before: PropTypes.func, // renders it's return value before promise is handled
then: PropTypes.func, // renders it's return value when promise is resolved
catch: PropTypes.func, // renders it's return value when promise is rejected
pending: PropTypes.node, // renders it's value when promise is pending
pending: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), // renders it's value when promise is pending
promise: PropTypes.object // promise itself
}

7 changes: 7 additions & 0 deletions src/react-promise.spec.js
Original file line number Diff line number Diff line change
@@ -25,6 +25,13 @@ describe('async', function () {
expect(wrapper.html()).toBe(null)
})

it('should render defaultPending when promise is pending', () => {
Async.defaultPending = 'Loooading'
const wrapper = mount(<Async promise={prom()} />)

expect(wrapper.text()).toBe(Async.defaultPending)
})

it('should render a supplied pending prop when promise is pending', function () {
const wrapper = mount(
<Async promise={prom()} pending={<span>Loading ...</span>} />

0 comments on commit 659d091

Please sign in to comment.