Skip to content

Commit 5e0676f

Browse files
committed
Merge remote-tracking branch 'origin/master'
# Conflicts: # package.json
2 parents bfd280f + a91f7bc commit 5e0676f

File tree

9 files changed

+2078
-1822
lines changed

9 files changed

+2078
-1822
lines changed

README.md

Lines changed: 74 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,80 @@ or NPM:
4747
npm install react-intersection-observer --save
4848
```
4949

50+
## Props
51+
52+
The **`<Observer />`** accepts the following props:
53+
54+
| Name | Type | Default | Required | Description |
55+
| --------------- | ----------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
56+
| **children** | Func/Node | | false | Children should be either a function or a node |
57+
| **render** | ({inView, ref}) => Node | | false | Render prop allowing you to control the view. |
58+
| **root** | HTMLElement | | false | The HTMLElement that is used as the viewport for checking visibility of the target. Defaults to the browser viewport if not specified or if null. |
59+
| **rootId** | String | | false | Unique identifier for the root element - This is used to identify the IntersectionObserver instance, so it can be reused. If you defined a root element, without adding an id, it will create a new instance for all components. |
60+
| **rootMargin** | String | '0px' | false | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). |
61+
| **tag** | String | 'div' | false | Element tag to use for the wrapping element when rendering using 'children'. Defaults to 'div' |
62+
| **threshold** | Number | 0 | false | Number between 0 and 1 indicating the the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. |
63+
| **triggerOnce** | Bool | false | false | Only trigger this method once |
64+
| **onChange** | Func | | false | Call this function whenever the in view state changes |
65+
66+
## Usage
67+
68+
### Child as function
69+
70+
The easiest way to use the `Observer`, is to pass a function as the child. It
71+
will be called whenever the state changes, with the new value of `inView`.
72+
By default it will render inside a `<div>`, but you can change the element by setting `tag` to the HTMLElement you need.
73+
74+
```js
75+
import Observer from 'react-intersection-observer'
76+
77+
const Component = () => (
78+
<Observer>
79+
{inView => <h2>{`Header inside viewport ${inView}.`}</h2>}
80+
</Observer>
81+
)
82+
83+
export default Component
84+
```
85+
86+
### Render prop
87+
88+
Using the render prop you can get full control over the output.
89+
In addition to the `inView` prop, the render also receives a `ref` that should be set on the containing DOM element.
90+
91+
```js
92+
import Observer from 'react-intersection-observer'
93+
94+
const Component = () => (
95+
<Observer
96+
render={({ inView, ref }) => (
97+
<div ref={ref}>
98+
<h2>{`Header inside viewport ${inView}.`}</h2>
99+
</div>
100+
)}
101+
/>
102+
)
103+
104+
export default Component
105+
```
106+
107+
### OnChange callback
108+
109+
You can monitor the onChange method, and control the state in your own
110+
component.
111+
112+
```js
113+
import Observer from 'react-intersection-observer'
114+
115+
const Component = () => (
116+
<Observer onChange={inView => console.log('Inview:', inView)}>
117+
<h2>Plain children are always rendered. Use onChange to monitor state.</h2>
118+
</Observer>
119+
)
120+
121+
export default Component
122+
```
123+
50124
### Polyfill for intersection-observer
51125

52126
The component requires the [intersection-observer
@@ -98,68 +172,3 @@ function supportsIntersectionObserver() {
98172
)
99173
}
100174
```
101-
102-
## Props
103-
104-
The **`<Observer />`** accepts the following props:
105-
106-
| Name | Type | Default | Required | Description |
107-
| --------------- | ----------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
108-
| **children** | func/node | | true | Children should be either a function or a node |
109-
| **root** | HTMLElement | | false | The HTMLElement that is used as the viewport for checking visibility of the target. Defaults to the browser viewport if not specified or if null. |
110-
| **rootId** | String | | false | Unique identifier for the root element - This is used to identify the IntersectionObserver instance, so it can be reused. If you defined a root element, without adding an id, it will create a new instance for all components. |
111-
| **rootMargin** | String | '0px' | false | Margin around the root. Can have values similar to the CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). |
112-
| **tag** | String | 'div' | false | Element tag to use for the wrapping component |
113-
| **threshold** | Number | 0 | false | Number between 0 and 1 indicating the the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. |
114-
| **triggerOnce** | Bool | false | false | Only trigger this method once |
115-
| **onChange** | Func | | false | Call this function whenever the in view state changes |
116-
| **render** | Func | | false | Render prop boolean indicating inView state |
117-
| **innerRef** | Func | | false | Get a reference to the the inner DOM node |
118-
119-
## Example code
120-
121-
### Child as function
122-
123-
The default way to use the `Observer`, is to pass a function as the child. It
124-
will be called whenever the state changes, with the new value of `inView`.
125-
126-
```js
127-
import Observer from 'react-intersection-observer'
128-
129-
const Component = () => (
130-
<Observer>
131-
{inView => <h2>{`Header inside viewport ${inView}.`}</h2>}
132-
</Observer>
133-
)
134-
135-
export default Component
136-
```
137-
138-
### Render prop
139-
140-
```js
141-
import Observer from 'react-intersection-observer'
142-
143-
const Component = () => (
144-
<Observer render={inView => <h2>{`Header inside viewport ${inView}.`}</h2>} />
145-
)
146-
147-
export default Component
148-
```
149-
150-
### OnChange callback
151-
152-
You can monitor the onChange method, and control the state in your own
153-
component. The child node will always be rendered.
154-
155-
```js
156-
import Observer from 'react-intersection-observer'
157-
158-
const Component = () => (
159-
<Observer onChange={inView => console.log('Inview:', inView)}>
160-
<h2>Plain children are always rendered. Use onChange to monitor state.</h2>
161-
</Observer>
162-
)
163-
164-
export default Component
165-
```

index.d.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1+
import { RenderProps } from 'react-intersection-observer'
2+
13
declare module 'react-intersection-observer' {
24
import React = require('react')
35

6+
export interface RenderProps {
7+
inView: boolean
8+
ref: Function
9+
}
10+
411
export interface IntersectionObserverProps {
512
/** Children should be either a function or a node */
613
children?: React.ReactNode | ((inView: boolean) => React.ReactNode)
714

815
/** Render prop boolean indicating inView state */
9-
render?: (inView: boolean) => React.ReactNode
16+
render?: (fields: RenderProps) => React.ReactNode
1017

1118
/**
1219
* The `HTMLElement` that is used as the viewport for checking visibility of
@@ -51,13 +58,12 @@ declare module 'react-intersection-observer' {
5158

5259
/** Call this function whenever the in view state changes */
5360
onChange?: (inView: boolean) => void
54-
55-
/** Get a reference to the the inner DOM node */
56-
innerRef?: (element?: HTMLElement) => void
5761
}
5862

5963
export default class IntersectionObserver extends React.Component<
6064
IntersectionObserverProps,
6165
{}
6266
> {}
6367
}
68+
69+
export default Component

package.json

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-intersection-observer",
3-
"version": "3.0.3",
3+
"version": "4.0.1-0",
44
"description": "Monitor if a component is inside the viewport, using IntersectionObserver API",
55
"main": "lib/index.js",
66
"author": "Daniel Schmidt",
@@ -25,17 +25,21 @@
2525
"dev": "concurrently -k -r 'jest --watch' 'npm run storybook'",
2626
"lint": "eslint {src,stories,tests}/. ",
2727
"precommit": "flow && lint-staged",
28-
"postcommit": "git reset",
29-
"prepublish": "npm run build",
28+
"postcommit": "git update-index --again",
29+
"prepare": "npm run build",
3030
"pretty": "prettier '**/*.{js,md,json}' --write",
3131
"storybook": "start-storybook -p 9000",
3232
"test": "eslint {src,stories,tests}/. && jest"
3333
},
3434
"lint-staged": {
35-
"*.js": [
36-
"prettier --write --no-semi --single-quote --trailing-comma all",
37-
"eslint",
35+
"*.{js,json,css,md,ts}": [
36+
"prettier --write",
3837
"git add"
38+
],
39+
"src/**/*.js": [
40+
"eslint",
41+
"jest --findRelatedTests",
42+
"flow force-recheck --focus"
3943
]
4044
},
4145
"eslintConfig": {
@@ -57,41 +61,39 @@
5761
"<rootDir>/jest-setup.js"
5862
]
5963
},
60-
"dependencies": {},
64+
"dependencies": {
65+
"invariant": "^2.2.4"
66+
},
6167
"peerDependencies": {
6268
"react": "^15.0.0 || ^16.0.0 || ^17.0.0"
6369
},
6470
"devDependencies": {
65-
"@storybook/addon-actions": "^3.3.12",
66-
"@storybook/addon-options": "^3.3.12",
67-
"@storybook/react": "^3.3.12",
71+
"@storybook/addon-actions": "^3.4.3",
72+
"@storybook/addon-options": "^3.4.3",
73+
"@storybook/react": "^3.4.3",
6874
"babel-cli": "^6.24.1",
69-
"babel-core": "^6.25.0",
70-
"babel-jest": "^22.1.0",
75+
"babel-core": "^6.26.3",
76+
"babel-jest": "^22.4.3",
7177
"babel-preset-env": "^1.6.1",
7278
"babel-preset-react": "^6.24.1",
7379
"babel-preset-stage-2": "^6.24.1",
7480
"babel-runtime": "^6.25.0",
7581
"concurrently": "3.5.1",
7682
"enzyme": "^3.3.0",
7783
"enzyme-adapter-react-16": "^1.0.4",
78-
"enzyme-to-json": "^3.3.1",
79-
"eslint": "^4.17.0",
80-
"eslint-config-insilico": "^5.1.0",
84+
"enzyme-to-json": "^3.3.3",
85+
"eslint": "^4.19.1",
86+
"eslint-config-insilico": "^5.2.0",
8187
"flow-bin": "^0.71.0",
82-
"flow-copy-source": "^1.2.2",
88+
"flow-copy-source": "^1.3.0",
8389
"husky": "^0.14.3",
8490
"intersection-observer": "^0.5.0",
85-
"jest": "^22.1.4",
91+
"jest": "^22.4.3",
8692
"lint-staged": "^7.0.0",
87-
"prettier": "^1.10.2",
88-
"react": "^16.2.0",
89-
"react-dom": "^16.2.0",
90-
"react-test-renderer": "^16.2.0",
91-
"request": "~2.84.0"
92-
},
93-
"resolutions": {
94-
"react": "^16.2.0",
95-
"react-dom": "^16.2.0"
93+
"prettier": "^1.12.1",
94+
"react": "^16.3.2",
95+
"react-dom": "^16.3.2",
96+
"react-test-renderer": "^16.3.2",
97+
"request": "~2.85.0"
9698
}
9799
}

src/index.js

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
// @flow
22
import * as React from 'react'
33
import { observe, unobserve } from './intersection'
4+
import invariant from 'invariant'
45

56
type Props = {
6-
/** Element tag to use for the wrapping */
7+
/** Element tag to use for the wrapping element when rendering using 'children'. Defaults to 'div' */
78
tag: string,
89
/** Only trigger the inView callback once */
910
triggerOnce: boolean,
1011
/** Children should be either a function or a node */
1112
children?: ((inView: boolean) => React.Node) | React.Node,
1213
/** Render prop boolean indicating inView state */
13-
render?: (inView: boolean) => React.Node,
14+
render?: ({
15+
inView: boolean,
16+
ref: (node: ?HTMLElement) => void,
17+
}) => React.Node,
1418
/** Number between 0 and 1 indicating the the percentage that should be visible before triggering. Can also be an array of numbers, to create multiple trigger points. */
1519
threshold?: number | Array<number>,
1620
/** The HTMLElement that is used as the viewport for checking visibility of the target. Defaults to the browser viewport if not specified or if null.*/
@@ -22,8 +26,6 @@ type Props = {
2226
rootId?: string,
2327
/** Call this function whenever the in view state changes */
2428
onChange?: (inView: boolean) => void,
25-
/** Get a reference to the the inner DOM node */
26-
innerRef?: Function,
2729
}
2830

2931
type State = {
@@ -50,6 +52,15 @@ class Observer extends React.Component<Props, State> {
5052
inView: false,
5153
}
5254

55+
componentDidMount() {
56+
if (typeof this.props.render === 'function') {
57+
invariant(
58+
this.node,
59+
`react-intersection-observer: No DOM node found. Make sure you forward "ref" to the root DOM element you want to observe, when using render prop.`,
60+
)
61+
}
62+
}
63+
5364
componentDidUpdate(prevProps: Props, prevState: State) {
5465
// If a IntersectionObserver option changed, reinit the observer
5566
if (
@@ -97,10 +108,6 @@ class Observer extends React.Component<Props, State> {
97108
if (this.node) unobserve(this.node)
98109
this.node = node
99110
this.observeNode()
100-
101-
if (this.props.innerRef) {
102-
this.props.innerRef(node)
103-
}
104111
}
105112

106113
handleChange = (inView: boolean) => {
@@ -115,7 +122,6 @@ class Observer extends React.Component<Props, State> {
115122
children,
116123
render,
117124
tag,
118-
innerRef,
119125
triggerOnce,
120126
threshold,
121127
root,
@@ -126,14 +132,16 @@ class Observer extends React.Component<Props, State> {
126132

127133
const { inView } = this.state
128134

135+
if (typeof render === 'function') {
136+
return render({ inView, ref: this.handleNode })
137+
}
138+
129139
return React.createElement(
130140
tag,
131141
{
132142
...props,
133143
ref: this.handleNode,
134144
},
135-
// If render is a function, use it to render content when in view
136-
typeof render === 'function' ? render(inView) : null,
137145
// If children is a function, render it with the current inView status.
138146
// Otherwise always render children. Assume onChange is being used outside, to control the the state of children.
139147
typeof children === 'function' ? children(inView) : children,

src/intersection.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ function onChange(changes) {
146146
inView = inView && isIntersecting
147147
}
148148

149+
instance.visible = inView
149150
if (instance.callback) {
150151
instance.callback(inView)
151152
}

0 commit comments

Comments
 (0)