diff --git a/README.md b/README.md index 0d6692c..d6f3d90 100644 --- a/README.md +++ b/README.md @@ -301,11 +301,9 @@ waypoint as a line across the page. Whenever that line crosses a [boundary](#offsets-and-boundaries), then the `onEnter` or `onLeave` callbacks will be called. -When children are passed, the waypoint's size will be determined by the -size of a div wrapping the contained children (or, with `noWrapper={true}`, -the size of the child element you pass). The `onEnter` callback will be called -when *any* part of the children is visible in the viewport. The `onLeave` -callback will be called when *all* children have exited the viewport. +If you do pass a child, it must be a single DOM Element (eg; a `
`). +The `onEnter` callback will be called when *any* part of the child is visible +in the viewport. The `onLeave` callback will be called when *all* of the child have exited the viewport. (Note that this is measured only on a single axis; strangely positioned elements may not work as expected). diff --git a/spec/waypoint_spec.js b/spec/waypoint_spec.js index 94fdd7a..1fde22b 100644 --- a/spec/waypoint_spec.js +++ b/spec/waypoint_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable react/no-multi-comp */ import React from 'react'; import ReactDOM from 'react-dom'; import Waypoint from '../src/waypoint.jsx'; @@ -343,7 +344,7 @@ describe('', function() { describe('with children', () => { beforeEach(() => { this.childrenHeight = 80; - this.props.children = [ + this.props.children = React.createElement('div', {}, [ React.createElement('div', { key: 1, style: { @@ -356,7 +357,7 @@ describe('', function() { height: this.childrenHeight / 2, } }), - ]; + ]); }); describe('when scrolling down far enough', () => { @@ -853,68 +854,35 @@ describe('', function() { }); }); - describe('when the Waypoint has children and is above the top', () => { - beforeEach(() => { - this.topSpacerHeight = 200; - this.bottomSpacerHeight = 200; - this.childrenHeight = 100; - this.props.children = React.createElement('div', { - style: { - height: this.childrenHeight, - } - }); - this.scrollable = this.subject(); + describe('when the Waypoint has children that are not DOM Elements', () => { + const errorMessage = 'You must wrap any Component Elements passed to Waypoint ' + + 'in a DOM Element (eg; a
).'; - // Because of how we detect when a Waypoint is scrolled past without any - // scroll event fired when it was visible, we need to reset callback - // spies. - scrollNodeTo(this.scrollable, 400); - this.props.onEnter.calls.reset(); - this.props.onLeave.calls.reset(); - scrollNodeTo(this.scrollable, 400); - }); + it('errors with a stateless component', () => { + const StatelessComponent = () => React.createElement('div'); + this.props.children = React.createElement(StatelessComponent); - it('does not call the onEnter handler', () => { - expect(this.props.onEnter).not.toHaveBeenCalled(); + expect(this.subject).toThrowError(errorMessage); }); - it('does not call the onLeave handler', () => { - expect(this.props.onLeave).not.toHaveBeenCalled(); - }); - - describe('when scrolled back up just past the bottom', () => { - beforeEach(() => { - scrollNodeTo(this.scrollable, this.topSpacerHeight + 50); - }); - - it('calls the onEnter handler', () => { - expect(this.props.onEnter). - toHaveBeenCalledWith({ - currentPosition: Waypoint.inside, - previousPosition: Waypoint.above, - event: jasmine.any(Event), - waypointTop: -40, - waypointBottom: -40 + this.childrenHeight, - viewportTop: this.margin, - viewportBottom: this.margin + this.parentHeight, - }); - }); + it('errors with a class-based component', () => { + class ClassBasedComponent extends React.Component { + render() { + return React.createElement('div'); + } + } + this.props.children = React.createElement(ClassBasedComponent); - it('does not call the onLeave handler', () => { - expect(this.props.onLeave).not.toHaveBeenCalled(); - }); + expect(this.subject).toThrowError(errorMessage); }); }); - describe('when noWrapper=true and child is above the top', () => { + describe('when the Waypoint has children and is above the top', () => { beforeEach(() => { this.topSpacerHeight = 200; this.bottomSpacerHeight = 200; this.childrenHeight = 100; - this.childRefSpy = jasmine.createSpy('ref'); - this.props.noWrapper = true; - this.props.children = React.createElement('section', { - ref: this.childRefSpy, + this.props.children = React.createElement('div', { style: { height: this.childrenHeight, } @@ -930,14 +898,6 @@ describe('', function() { scrollNodeTo(this.scrollable, 400); }); - it('calls the original ref handler', () => { - expect(this.childRefSpy).toHaveBeenCalled(); - }); - - it('does not have an extra div', () => { - expect(this.scrollable.children[1].nodeName).toBe('SECTION'); - }); - it('does not call the onEnter handler', () => { expect(this.props.onEnter).not.toHaveBeenCalled(); }); diff --git a/src/waypoint.jsx b/src/waypoint.jsx index 7628f8e..994fcba 100644 --- a/src/waypoint.jsx +++ b/src/waypoint.jsx @@ -120,6 +120,13 @@ function computeOffsetPixels(offset, contextHeight) { } } +/** + * @param {React.element} Component + * @return {bool} Whether the component is a DOM Element + */ +function isDomElement(Component) { + return (typeof Component.type === 'string'); +} /** * Calls a function when you scroll to the element. @@ -352,8 +359,13 @@ export default class Waypoint extends React.Component { * @return {Object} */ render() { - if (this.props.noWrapper) { + if (this.props.children) { const child = React.Children.only(this.props.children); + if (!isDomElement(child)) { + throw new Error( + 'You must wrap any Component Elements passed to Waypoint in a DOM Element (eg; a
).' + ); + } const ref = (node) => { this.refElement(node); if (this.props.children.ref) { @@ -363,10 +375,6 @@ export default class Waypoint extends React.Component { return React.cloneElement(child, { ref }); } - if (this.props.children) { - return
{this.props.children}
; - } - // We need an element that we can locate in the DOM to determine where it is // rendered relative to the top of its context. return ; @@ -374,8 +382,7 @@ export default class Waypoint extends React.Component { } Waypoint.propTypes = { - children: PropTypes.node, - noWrapper: PropTypes.bool, + children: PropTypes.element, debug: PropTypes.bool, onEnter: PropTypes.func, onLeave: PropTypes.func,