Skip to content
Permalink
Browse files
Update documentation with more correct mapPropsToRequestsToProps
  • Loading branch information
ryanbrainard committed Nov 12, 2015
1 parent 42823f6 commit 7d6b282fe873fd4cbcaaf72ed992b7d79a6a6840
Showing with 50 additions and 20 deletions.
  1. +1 −1 LICENSE.md
  2. +31 −3 README.md
  3. +6 −4 docs/api.md
  4. +5 −5 src/components/connect.js
  5. +7 −7 test/components/connect.spec.js
@@ -22,7 +22,7 @@ SOFTWARE.

---

License for portions of React Redux project from which this was derived project:
License for portions of React Redux project from which this was derived:

The MIT License (MIT)

@@ -4,11 +4,39 @@ React Refetch
**EXPERIMENTAL: Use with caution**

React bindings for URL data.
This allows you to easily fetch data from one or more URLs to provide to a React component as props.

[![build status](https://img.shields.io/travis/heroku/react-refetch/master.svg?style=flat-square)](https://travis-ci.org/heroku/react-refetch) [![npm version](https://img.shields.io/npm/v/react-refetch.svg?style=flat-square)](https://www.npmjs.com/package/react-refetch)
[![npm downloads](https://img.shields.io/npm/dm/react-refetch.svg?style=flat-square)](https://www.npmjs.com/package/react-refetch)

This project was inspired by (and forked from) [react-redux](https://github.com/rackt/react-redux). Redux/Flux is a wonderful library/pattern for applications that need to maintain complicated client-side state; however, if your application is just fetching and rendering read-only data from a server, it can over-complicate the architecture to fetch data in actions, reduce it into the store, only to select it back out again. The other approach of fetching data [inside](https://facebook.github.io/react/tips/initial-ajax.html) the component and dumping it in local state is also messy and makes components smarter and more mutable than they need to be. This module allows you to wrap a component in a `connect()` decorator like react-redux, but instead of mapping state to props, this let's you map props to URLs (or `Request`s) to props.

For example, if you have a component called `Profile` that has a `userId` prop, you can wrap it in `connect()` to map `userId` to one or more URLs and assigned to new props called `userFetch` and `likesFetch`:

connect(props => {
return {
userFetch: `/users/${props.userId}`
likesFetch: `/likes/${props.userId}/likes`
}
})(Profile)

When the component mounts, the URLs will be calculated, fetched, and the result will be passed into the component as the props specified. The result is represented as a `PromiseState`, which is a synchronous representation of the fetch `Promise`. It will either be `pending`, `fulfilled`, or `rejected`. This makes it simple to reason about the fetch state at the point in time the component is rendered:

render() {
const { userFetch, likesFetch } = this.props

if (userFetch.pending) {
return <LoadingAnimation/>
} else if (userFetch.rejected) {
return <Error error={userFetch.reason}/>
} else if (userFetch.fulfilled) {
return <User data={userFetch.value}/>
}

// similar for `likesFetch`
}

When new props are received, the URLs are re-calculated, and if they changed, the data is refetched and passed into the component as new `PromiseState`s.

## Installation

Requires **React 0.14 or later.**
@@ -22,8 +50,8 @@ This assumes that you’re using [npm](http://npmjs.com/) package manager with a
## Documentation

- [API](https://github.com/heroku/react-refetch/blob/master/docs/api.md)
- [`connect([mapPropsToRequests])`](https://github.com/heroku/react-refetch/blob/master/docs/api.md#connectmappropstorequests)
- [`connect([mapPropsToRequestsToProps])`](https://github.com/heroku/react-refetch/blob/master/docs/api.md#connectmappropstorequeststoprops)

## License

MIT
[MIT](https://github.com/heroku/react-refetch/blob/master/LICENSE.md)
@@ -1,6 +1,6 @@
## API

### `connect([mapPropsToRequests])`
### `connect([mapPropsToRequestsToProps])`

Connects a React component to data from one or more URLs.

@@ -9,7 +9,7 @@ Instead, it *returns* a new, connected component class, for you to use.

#### Arguments

* [`mapPropsToRequests(props): urlProps`] \(*Function*): If specified, the component will fetch data from the URLs. Any time props update, `mapPropsToRequests` will be called. Its result must be a plain object mapping prop keys to URL strings or `window.Request` objects. If the values changed, they will be passed to `window.fetch` and the synchronous state of the resulting promise will be serialized and merged into the component’s props. If you omit it, the component will not be connected to any URLs.
* [`mapPropsToRequestsToProps(props): urlProps`] \(*Function*): If specified, the component will fetch data from the URLs. Any time props update, `mapPropsToRequestsToProps` will be called. Its result must be a plain object mapping prop keys to URL strings or `window.Request` objects. If the values changed, they will be passed to `window.fetch` and the synchronous state of the resulting promise will be serialized and merged into the component’s props. If you omit it, the component will not be connected to any URLs.

* [`options`] *(Object)* If specified, further customizes the behavior of the connector.
* [`withRef = false`] *(Boolean)*: If true, stores a ref to the wrapped component instance and makes it available via `getWrappedInstance()` method. *Defaults to `false`.*
@@ -41,7 +41,7 @@ Returns the wrapped component instance. Only available if you pass `{ withRef: t

#### Example

// create a dumb component that receives data as props
// create a component that receives data as props
class Profile extends React.Component {
static propTypes = {
params: PropTypes.shape({
@@ -51,6 +51,8 @@ Returns the wrapped component instance. Only available if you pass `{ withRef: t
likesFetch: PropTypes.instanceOf(PromiseState)
}
render() {
const { userFetch, likesFetch } = this.props

// render the different promise states of user
if (userFetch.pending) {
return <LoadingAnimation/>
@@ -64,7 +66,7 @@ Returns the wrapped component instance. Only available if you pass `{ withRef: t
}

// declare the URLs for fetching the data assigned to keys and connect the component.
export default connect((props) => {
export default connect(props => {
return {
userFetch: `/users/${props.params.userId}`
likesFetch: `/likes/${props.params.userId}/likes`
@@ -5,7 +5,7 @@ import PromiseState from '../PromiseState'
import hoistStatics from 'hoist-non-react-statics'
import invariant from 'invariant'

const defaultMapPropsToRequests = () => ({})
const defaultMapPropsToRequestsToProps = () => ({})

function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component'
@@ -14,19 +14,19 @@ function getDisplayName(WrappedComponent) {
// Helps track hot reloading.
let nextVersion = 0

export default function connect(mapPropsToRequests, options = {}) {
const finalMapPropsToRequests = mapPropsToRequests || defaultMapPropsToRequests
export default function connect(mapPropsToRequestsToProps, options = {}) {
const finalMapPropsToRequestsToProps = mapPropsToRequestsToProps || defaultMapPropsToRequestsToProps
const { withRef = false } = options

// Helps track hot reloading.
const version = nextVersion++

function computeRequests(props) {
const urlsOrRequests = finalMapPropsToRequests(props) || {}
const urlsOrRequests = finalMapPropsToRequestsToProps(props) || {}

invariant(
isPlainObject(urlsOrRequests),
'`mapPropsToRequests` must return an object. Instead received %s.',
'`mapPropsToRequestsToProps` must return an object. Instead received %s.',
urlsOrRequests
)

@@ -86,7 +86,7 @@ describe('React', () => {
expect('x' in propsAfter).toEqual(false, 'x prop must be removed')
})

it('should invoke mapPropsToRequests every time props are changed', () => {
it('should invoke mapPropsToRequestsToProps every time props are changed', () => {
let propsPassedIn
let invocationCount = 0

@@ -182,10 +182,10 @@ describe('React', () => {
expect(spy.calls.length).toBe(4)
})

it('should throw an error if mapPropsToRequests returns anything but a plain object', () => {
function makeContainer(mapPropsToRequests) {
it('should throw an error if mapPropsToRequestsToProps returns anything but a plain object', () => {
function makeContainer(mapPropsToRequestsToProps) {
return React.createElement(
connect(mapPropsToRequests)(
connect(mapPropsToRequestsToProps)(
class Container extends Component {
render() {
return <Passthrough />
@@ -201,19 +201,19 @@ describe('React', () => {
TestUtils.renderIntoDocument(
makeContainer(() => 1)
)
}).toThrow(/mapPropsToRequests/)
}).toThrow(/mapPropsToRequestsToProps/)

expect(() => {
TestUtils.renderIntoDocument(
makeContainer(() => 'hey')
)
}).toThrow(/mapPropsToRequests/)
}).toThrow(/mapPropsToRequestsToProps/)

expect(() => {
TestUtils.renderIntoDocument(
makeContainer(() => new AwesomeMap())
)
}).toThrow(/mapPropsToRequests/)
}).toThrow(/mapPropsToRequestsToProps/)
})

it('should set the displayName correctly', () => {

0 comments on commit 7d6b282

Please sign in to comment.