Git Product home page Git Product logo

react-intersection-observer's Introduction

React Intersection Observer

Build Status Codecov NPM version Dowloads per week


Bring ReactIntersectionObserver over today, your React children will love it!

React Intersection Observer is a React component, acting as a wrapper for the IntersectionObserver API. It is fully declarative and takes care of all the imperative parts for you.

React Intersection Observer is good at:

  • reusing instances: comparing the passed options
  • performance: chooses smartly when to re-render and when to re-observe
  • being unopinionated: how to handle visibility changes is left entirely up to the developer
  • being intuitive: looks like the Native API

Table of Contents

Getting started

npm install --save @researchgate/react-intersection-observer

⚠️ Please make sure you have the minimum node version installed (as defined in the package.json)

Otherwise you run into this build error:

The engine "node" is incompatible with this module. Expected version ">=10.18.1". Got "10.15.3"

Usage

import React from 'react';
import 'intersection-observer'; // optional polyfill
import Observer from '@researchgate/react-intersection-observer';

class ExampleComponent extends React.Component {
  handleIntersection(event) {
    console.log(event.isIntersecting);
  }

  render() {
    const options = {
      onChange: this.handleIntersection,
      root: '#scrolling-container',
      rootMargin: '0% 0% -25%',
    };

    return (
      <div id="scrolling-container" style={{ overflow: 'scroll', height: 100 }}>
        <Observer {...options}>
          <div>I am the target element</div>
        </Observer>
      </div>
    );
  }
}

Optionally add the polyfill and make sure it's required on your dependendencies for unsupporting browsers:

npm install --save intersection-observer

What does IntersectionObserver do?

IntersectionObservers calculate how much of a target element overlaps (or "intersects with") the visible portion of a page, also known as the browser's "viewport":

Dan Callahan · Creative Commons License

Graphic example

Why use this component?

The motivation is to provide the easiest possible solution for observing elements that enter the viewport on your React codebase. It's fully declarative and all complexity is abstracted away, focusing on reusability, and low memory consumption.

No bookkeeping

It's built with compatibility in mind, adhering 100% to the native API implementation and DSL, but takes care of all the bookkeeping work for you.

Instances and nodes are managed internally so that any changes to the passed options or tree root reconciliation cleans up and re-observes nodes on-demand to avoid any unexpected memory leaks.

No extra markup

ReactIntersectionObserver does not create any extra DOM elements, it attaches to the only child you'll provide to it. This is done using findDOMNode to retrieve the first DOM node found. If your child already has an existing ref, either a callback or object (from createRef), these will be handled normally in either case.

Easy to adopt

When using ReactIntersectionObserver the only required prop is the onChange function. Any changes to the visibility of the element will invoke this callback, just like in the native API - you’ll receive one IntersectionObserverEntry argument per change. This gives you an ideal and flexible base to build upon.

Some of the things you may want to use ReactIntersectionObserver for:

Documentation

Demos

Find multiple examples and usage guidelines under: https://researchgate.github.io/react-intersection-observer/

demo

Recipes

Recipes are useful code snippets solutions to common problems, for example, how to use ReactIntersectionObserver within a Higher Order Component.
Here's how to create an element monitoring component:

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Observer from '@researchgate/react-intersection-observer';

export default class ViewableMonitor extends Component {
  static propTypes = {
    tag: PropTypes.node,
    children: PropTypes.func.isRequired,
  };

  static defaultProps = {
    tag: 'div',
  };

  state = {
    isIntersecting: false,
  };

  handleChange = ({ isIntersecting }) => {
    this.setState({ isIntersecting });
  };

  render() {
    const { tag: Tag, children, ...rest } = this.props;

    return (
      <Observer {...rest} onChange={this.handleChange}>
        <Tag>{children(this.state.isIntersecting)}</Tag>
      </Observer>
    );
  }
}
import React from 'react';
import ViewableMonitor from './ViewableMonitor';

export default () => (
  <ViewableMonitor>
    {(isViewable) => (isViewable ? 'I am viewable' : 'I am still hiding')}
  </ViewableMonitor>
);

Discover more recipes in our examples section.

Missing DOM nodes when observing

In cases where there isn't a DOM node available to observe when rendering, you'll be seeing an error logged in the console:

ReactIntersectionObserver: Can't find DOM node in the provided children. Make sure to render at least one DOM node in the tree.

This somewhat helpful and descriptive message is supposed to help you identify potential problems implementing observers early on. If you miss the exception for some reason and ends up in production (prone to happen with dynamic children), the entire tree will unmount so be sensible about placing your error boundaries.

Ultimately the way to avoid this is to either make sure you are rendering a DOM node inside your <Observer>, or to disable the observer until there's one <Observer disabled>.

Options

root: HTMLElement|string | default window object

The element or selector string that is used as the viewport for checking visibility of the target.

rootMargin: string | default 0px 0px 0px 0px

Margin around the root. Specify using units px or % (top, right, bottom left). Can contain negative values.

threshold: number|Array<number> | default: 0

Indicates at what percentage of the target's visibility the observer's callback should be executed. If you only want to detect when visibility passes the 50% mark, you can use a value of 0.5. If you want the callback run every time visibility passes another 25%, you would specify the array [0, 0.25, 0.5, 0.75, 1].

disabled: boolean | default: false

Controls whether the element should stop being observed by its IntersectionObserver instance. Useful for temporarily disabling the observing mechanism and restoring it later.

onChange (required): (entry: IntersectionObserverEntry, unobserve: () => void) => void

Function that will be invoked whenever an observer's callback contains this target in its changes.

children: React.Element<*>|null

Single React component or element that is used as the target (observable). As of v1.0.0, children can be null. Null children won't be observed.

Notes

  • According to the spec, an initial event is being fired when starting to observe a non-intersecting element as well.
  • Changes happen asynchronously, similar to the way requestIdleCallback works.
  • Although you can consider callbacks immediate - always below 1 second - you can also get an immediate response on an element's visibility with observer.takeRecords().
  • The primitives Map an Set are required. You may need to include a polyfill for browsers lacking ES2015 support. If you're using babel, include "babel-polyfill" somewhere to your codebase.

Polyfill

When needing the full spec's support, we highly recommend using the IntersectionObserver polyfill.

Caveats

Ealier Spec

Earlier preview versions of Edge and prior to version 58 of Chrome, the support for isIntersecting was lacking. This property was added to the spec later and both teams where unable to implement it earlier.

Performance issues

As the above-mentioned polyfill doesn't perform callback invocation asynchronously, you might want to decorate your onChange callback with a requestIdleCallback or setTimeout call to avoid a potential performance degradation:

onChange = (entry) => requestIdleCallback(() => this.handleChange(entry));

Out of the box

Chrome 51 [1]
Firefox (Gecko) 55 [2]
MS Edge 15
Internet Explorer Not supported
Opera [1] 38
Safari 12.1
Chrome for Android 59
Android Browser 56
Opera Mobile 37
  • [1]reportedly available, it didn't trigger the events on initial load and lacks isIntersecting until later versions.
  • [2] This feature was implemented in Gecko 53.0 (Firefox 53.0 / Thunderbird 53.0 / SeaMonkey 2.50) behind the preference dom.IntersectionObserver.enabled.

Using polyfill

Safari 6+
Internet Explorer 7+
Android 4.4+

Contributing

We'd love your help on creating React Intersection Observer!

Before you do, please read our Code of Conduct so you know what we expect when you contribute to our projects.

Our Contributing Guide tells you about our development process and what we're looking for, gives you instructions on how to issue bugs and suggest features, and explains how you can build and test your changes.

Haven't contributed to an open source project before? No problem! Contributing Guide has you covered as well.

react-intersection-observer's People

Contributors

bryandowning avatar chambo-e avatar danez avatar dependabot[bot] avatar dwick avatar eugene1g avatar francoischalifour avatar greenkeeper[bot] avatar haeusler avatar heygrady avatar jackfranklin avatar joaovieira avatar merri avatar polamjag avatar rendez avatar renovate-bot avatar renovate[bot] avatar saitonakamura avatar semantic-release-bot avatar stefanhoth avatar stryju avatar weaintplastic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-intersection-observer's Issues

Support React 17; update peerDependencies

I would like to upgrade my project to React 17 but this dependency lists peerDependencies of React 16. Peer Dependencies should be updated to be React 16 || React 17. It doesn't look like any of your code uses deprecated APIs, so it should all just work in React 17 as well.

Rm "module": "lib/es/src/index.js", from package.json

Expected behavior

Our webpack build shouldn't fail :).

Current behavior

For some reason I don't completely understand, our webpack build is failing because of this line in package.json:

  "module": "lib/es/src/index.js",

I don't even know what the module field in package.json does (newb ..). But it seems to be causing ES6 Javascript to leak into our built web code. Is it needed to package es code?

Steps to reproduce

Can only provide steps to not reproduce: rm line:

  "module": "lib/es/src/index.js",

Context (environment)

Node: v12.18.1
react-intersection-observer: v1.3.3
webpack: v3.

Using Yarn to install Library fails

Expected behavior

Successfully install react-intersection-observer to my react application

Current behavior

gets the error message below
error @researchgate/[email protected]: The engine "node" is incompatible with this module. Expected version ">=10.18.1". Got "10.15.3"

Steps to reproduce

yarn add @researchgate/react-intersection-observer

Enhancement: Does this actually not work with React v15?

Current package.json:

  "peerDependencies": {
    "react": "^16.3.2",
    "react-dom": "^16.3.2"
  },

I'm not sure, but seems to work fine with React v15. What specifically is required from React v16? If nothing, could I please make a "suggested enhancement":

  "peerDependencies": {
    "react": ">=15",
    "react-dom": ">=15"
  },

If it does require v16, it would be great if you could add a quick note in the README about why.

Error boundary catches errors of child components

Expected behavior

An error thrown by a child of the intersection observer should end up in the user deifned error boundary that was placed outside of the intersection observer.

Current behavior

The intersection observer catches all errors, istead of only handling a missing DOM node situation.

Steps to reproduce

Render an app like this:

import React from 'react';
import IntersectionObserver from '@researchgate/react-intersection-observer';


class MyErrorBoundary extends React.Component {
  state = {hasError: false};

  componentDidCatch(error, info) {
    console.log('I am not called!');
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    console.log('I am called?');

    return this.state.hasError ? 'oy' : this.props.children;
  }
}

class ChildWithError extends React.Component {
  render() {
    return (
      <button
        onClick={() => {
          this.setState(() => {
            throw new Error('Some forbidden Button was clicked! ');
          });
        }}
      >
        click here to trigger error
      </button>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <MyErrorBoundary>
        <IntersectionObserver onChange={() => {}} threshold={0.5}>
          <div>
            <ChildWithError />
          </div>
        </IntersectionObserver>
      </MyErrorBoundary>
    );
  }
}

You will find the console.log('I am not called!'); is not called.

Context (environment)

  • Version: 1.1.0 / 1.1.1
  • Platform: Darwin host 18.7.0 Darwin Kernel Version 18.7.0: Thu Jan 23 06:52:12 PST 2020; root:xnu-4903.278.25~1/RELEASE_X86_64 x86_64

Missing type definitions since 1.3.0

Expected behavior

No TypeScript errors.

Current behavior

TypeScript outputs the error:

Cannot find module '@researchgate/react-intersection-observer' or its corresponding type declarations.ts(2307)

image

Steps to reproduce

See https://codesandbox.io/s/nostalgic-shadow-qz6gh?file=/src/App.tsx

Context (environment)

  • Version: 1.3.0
  • Platform: Darwin F200420NT01.local 19.6.0 Darwin Kernel Version 19.6.0: Sun Jul 5 00:43:10 PDT 2020; root:xnu-6153.141.1~9/RELEASE_X86_64 x86_64

Library not working on iOS 12 and Next.js

Looks like the library is miss behaving for iOS version below 12, but I am not sure 100%.
On Android, Windows and Linux it works normally.

Expected behavior

Working normally

Current behavior

I receive this error

invocation exception on Object.childNodeCountUpdated(): TypeError: Cannot set property '_childNodeCount' of undefined

This is happening only in iOS and I could test only with version 12.0.1
I am using next.js I am not sure is happening with vanilla react.js also.

Steps to reproduce

I will try to provide a codesandbox and more information as I keep investigating, but it should pretty easy to reproduce.

Context (environment)

I am using:

Next.js: 8.0.3
react: 16.8.1,
react-dom: 16.8.1
Chrome: 73.0
  • Version: 0.7.4
  • Platform: iOS 12.0.1

onChange arguments not in docs

Hello!

I was reading docs for the first time and it was unclear what arguments onChange receive. I had to open examples and get idea from them, or I could open src files.

I suppose, we should add this information to documentation, for better dev. experience. If you are agree, I can do it :)

Not working in Edge?

Hi. I'm getting unexpected intersectionRatio values in Edge in an app I'm building and came here to check if someone else had the same issue. I then tried your storybook examples in Edge and looks like only the Thresholds and Margin examples seem to be working.

Here’s a video with the versions used: https://d.pr/v/kgwVnn

In my app, the issue is intersectionRatio coming back as 1 for a 1px height element that is not within the viewport.

Storybook failing to find variable: exports

Thanks for taking the time to file an issue with us.
Please note that this issue template is used ONLY for reporting bugs.

If you have an issue that isn’t a bug, please follow the steps listed in the Contributing.
Thanks!

Expected behavior

https://researchgate.github.io/react-intersection-observer/?path=/story/* should open without errors.

Current behavior

In Chrome:

ReferenceError: exports is not defined
    at Module.728 (https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:62602)
    at __webpack_require__ (https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1301)
    at Object.28 (https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:1765)
    at __webpack_require__ (https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1301)
    at Object.309 (https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:20958)
    at __webpack_require__ (https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1301)
    at Module.<anonymous> (https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:60576)
    at Module.641 (https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:62225)
    at __webpack_require__ (https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1301)
    at https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:59968

In Safari:

Can't find variable: exports
https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:62609
__webpack_require__@https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1305
https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:1784
__webpack_require__@https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1305
https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:20977
__webpack_require__@https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1305
https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:60595
https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:62229
__webpack_require__@https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1305
https://researchgate.github.io/react-intersection-observer/vendors~main.28ed3cace93a70069515.bundle.js:2:592055
render@https://researchgate.github.io/react-intersection-observer/vendors~main.28ed3cace93a70069515.bundle.js:2:572877
https://researchgate.github.io/react-intersection-observer/vendors~main.28ed3cace93a70069515.bundle.js:2:573096
configure@https://researchgate.github.io/react-intersection-observer/vendors~main.28ed3cace93a70069515.bundle.js:2:595005
https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:59948
https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:60008
__webpack_require__@https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1305
https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:59273
__webpack_require__@https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:1305
checkDeferredModules@https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:976
webpackJsonpCallback@https://researchgate.github.io/react-intersection-observer/runtime~main.28ed3cace93a70069515.bundle.js:1:665
global code@https://researchgate.github.io/react-intersection-observer/main.28ed3cace93a70069515.bundle.js:1:51

Steps to reproduce

  1. Go to https://researchgate.github.io/react-intersection-observer/?path=/story/*.
  2. An error is found.

Context (environment)

  • Version:
  • Platform: macOS Catalina

[Bug] Module 'invariant' not found on production environments

Expected behavior

The invariant module appears to be used only for debugging purposes on non-production environments, so it shouldn't need to be installed on production environments.

Current behavior

When building my project with webpack for production, an error is thrown:

ERROR in ./node_modules/@researchgate/react-intersection-observer/lib/es/IntersectionObserver.js
Module not found: Error: Can't resolve 'invariant' in '~/Projects/website/node_modules/@researchgate/react-intersection-observer/lib/es'
 @ ./node_modules/@researchgate/react-intersection-observer/lib/es/IntersectionObserver.js 12:0-34
 @ ./node_modules/@researchgate/react-intersection-observer/lib/es/index.js
 @ ./src/components/ProductPaymentMethods.js
 @ ./src/pages/Page.js
 @ ./src/App.js
 @ ./server/render.js
 @ ./server/app.js
 @ ./server/index.js

I assume this happens since npm doesn't install devDependencies on production environments. IntersectionObserver tries to import invariant (although it's not used) and then fails to find it.

Steps to reproduce

  1. Use IntersectionObserver in a project
  2. Compile the project with webpack in a production environment
  3. Tada! 🎉

Context (environment)

  • Version: 0.3.0
  • Platform: x86_64 GNU/Linux

onlyOnce ambiguous behavior on first event

Expected behavior

I expect when using onlyOnce option that unobserving should happen only after the observed item becomes visible, based on the threshold parameter (or in the case of an array of thresholds, when the first threshold is met)

Current behavior

In latest version of Chrome, the component stops observing even though the item is never visible. This is because Chrome can emit an event such that isIntersecting is true but intersectionRatio is still 0. I believe the spec requires that isIntersecting be true and intersectionRatio > 0 for something to be considered visible.

Steps to reproduce

Not sure exactly how to recreate the conditions necessary for Chrome to emit such an event...

  • Version: latest

Adding an additional condition before unobserving here:

if (event.isIntersecting) {

to also check for intersectionRatio != 0 fixed the issue for me.

Warning about missing getDerivedStateFromError in error boundary

Expected behavior

No React warnings should be logged from usage of intersection observer.

Current behavior

When an error is created inside intersection observer, the following warning is created by React:

react_devtools_backend.js:6 Warning: ErrorBoundary(IntersectionObserver): Error boundaries should implement getDerivedStateFromError(). In that method, return a state update to display an error message or fallback UI.
    in ErrorBoundary(IntersectionObserver) (created by IntersectionObserver)
    in IntersectionObserver (created by App)
    // ...

The warning was added in React 16.6.0 (facebook/react#13746)

Steps to reproduce

You can use the code from #101 to create an example error. You will see the warning in the developer console when React runs in development mode.

Context (environment)

  • Version: 1.1.0 / 1.1.1
  • Platform: Darwin host 18.7.0 Darwin Kernel Version 18.7.0: Thu Jan 23 06:52:12 PST 2020; root:xnu-4903.278.25~1/RELEASE_X86_64 x86_64

codecov and storybook issues in build

  • There is something failing right now in travis builds regarding codecov. Furthermore, TS sources aren't taken into account for coverage atm.
  • Storybook doesn't get published anymore, also failing in travis.

Drop support for pre-16.3 React versions

To improve the way we handle ref assignment and updates to observer targets, we need to use a more efficient way. This is possible with the lifecycle methods update introduced in React 16.3. The plan is to stop supporting previous versions to that and perhaps bump this change as v1.0.0.
More details in this previous thread:
#38 (comment)

Not observing when rerendered

I set each item(img) to have dataset and create a src attribute when its Intersecting = hot

but when I try to render different component eg. top_rated and rendered hot again it cannot create a src attribute

I set every item(MovieCard) to unobserve() after intersecting

code:

const handleChange = (entry, unobserve) => {
if (entry.isIntersecting) {
entry.target.setAttribute('src', entry.target.dataset.image);
unobserve();
}
};

const [ref] = useIntersectionObserver(handleChange);

items:

npm is invalid on all releases

All the releases on npmJs are invalid since yesterday/today (I used 1.2.0v yesterday at 7:00 PM).

I can't use it anymore and it is very critical because it blocks my CI/CD production flow

Observer type error

Expected behavior

Webpack should compile, no type error should occur

Current behavior

Webpack throws a typescript error

Steps to reproduce

const Component = () => {
  const handleIntersection = (event: any) => {
    console.log(event);
  }

  return (
    <Observer onChange={handleIntersection}>
      <div>Content</div>
    </Observer>
  );
}

I tried also something like this

  const handleIntersection = (event: IntersectionObserverEntry) => {
    console.log(event);
  }

Context (environment)

Error:

TS2322: Type '{ children: any[]; onChange: (event: IntersectionObserverEntry) => null; }' is not assignable to type 'Readonly<Props>'.
  Types of property 'children' are incompatible.
    Type 'any[]' is missing the following properties from type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)>': type, props, key
  • Version: ^1.0.1
  • Platform: Linux 4.15.0-47-generic #50-Ubuntu SMP Wed Mar 13 10:44:52 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Support React.RefObject in React >=16.3

Handle children ref created using the new React.createRef. This is an object containing a current property pointing to the HTML element. E.g.

import Observer from '@researchgate/react-intersection-observer';

class MyComponent extends React.Component {
  targetRef = React.createRef();

  handleClick = () => {
    console.log(this.targetRef.current); // HTMLDivElement
  }

  render () {
    return <Observer>
        <div ref={this.targetRef} onClick={this.handleClick}>Foobar</div>
    </Observer>
  }
}

I've raised this with the React team before. See: facebook/react#13029.

We need to extend here to support this new format.

Expected behavior

☝️ this.targetRef.current should be defined and should match HTMLDivElement.

Current behavior

☝️ this.targetRef.current is null.

Context (environment)

React >= 16.3. See facebook/react#13029.

Migrate to hooks to reduce size

I took at the source code and found several places where usage of hooks would help reduce size (though I gotta say, 1.7 kb is still impressive) and maybe simplify the code (reactive props via dependencies, for instance). I would take a shot at it, but there's a problem with backwards compatibility: hooks are 16.8+, and current peer deps are 16.3+

I'd like to hear your thoughts on that, maybe new major version is an option

Use of for...of is forcing use of Symbol polyfill

for (const observer of storage.keys()) {

for (const element of elements.values()) {

Although Symbol is not used directly by this package, the use of for...of is forcing anyone using it to add the Symbol polyfill to their project (if they want to support IE11). This isn't the end of the world but in my case it's adding a few KB to my polyfill bundle.

I understand that for...of is more convenient than the equivalent for (let i = 0; i < array.length; i++) loop we'd need instead. However, a relatively small change in this library would mean that consumers that don't otherwise need Symbol won't need to add a sizable polyfill to their project.

The other primitive requirements for Map and Set align with the environment recommendations for React.

related to #16

Ability access the ref instance so I can use react-intersection-observer with existing refs

Hey there, I'm not sure if this is a feature request or just me misunderstanding how this library should work.

I'm working on a catalog page where I have a dropdown navigation with categories, and when you select one (ex. vegetables), it scrolls to that category section of vegetables. That part works fine without this library, but I wanted to add the ability to update the dropdown with the section currently is in view, so I'm trying to do that with the react-intersection-observer hook.

I made a Codepen of what my code looks like now:
https://codepen.io/kauffmanes/pen/poNPYyW

And this is what I was trying to do using react-intersection-observer:

// doesn't do anything yet
const handleChange = (entry) => {
  console.log(entry.isIntersecting);
}

const categoryRefs = categories.reduce((acc, value) => {
    const [ref] = useIntersectionObserver(handleChange, {
        threshold: 0,
        rootMargin: '0% 0% -75%'
    });
    acc[value.id] = ref;
    return acc;
}, {});

/* currentCategory is a prop reflecting the ID of the currently selected category, ex. 'vegetables'. 
So when currentCategory changes, the effect is called. This code obviously doesn't work anymore 
because the ref returned by useIntersectionObserver doesn't include the instance. Is there another way
 I can access the instance so I can scroll to it? Or, should I be maintaining two refs? */
useEffect(() => {
    const scrollableRef = categoryRefs[currentCategory];
    if (scrollableRef) {
        const offsetTop =
            scrollableRef.current.offsetTop >= 100
                ? scrollableRef.current.offsetTop - 100
                : scrollableRef.current.offsetTop;
        window.scrollTo(0, offsetTop);
    }
}, [currentCategory]);

// in the render method:
<>
    {categories.map((category) => (
        <div ref={categoryRefs[category.id]}>{category.title}</div>
        // other stuff
    )}
</>

Expected behavior

I want to be able to use the refs created by react-interaction-observer so I get your library functionality in addition to the scrolling functionality I already had.

Current behavior

useIntersectionObserver doesn't return an instance that I can use so I'm unable to use this library without breaking existing functionality. If this is possible, I can try to submit a storybook recipe once it's working.

Context (environment)

  • Version: React 16.8.6
  • Platform: Darwin Emilys-MacBook-Pro.local 19.6.0 Darwin Kernel Version 19.6.0: Tue Nov 10 00:10:30 PST 2020; root:xnu-6153.141.10~1/RELEASE_X86_64 x86_64

IE / Edge

Expected behavior

According to docs, this component should work with IE11 with a polyfill, and with Edge 15 without a polyfill.

Current behavior

I am unable to even get the storybook example to work in Edge 15. The onChange action logger reports undefined for "isIntersecting".

Steps to reproduce

  1. Open the storybook example in Edge 15
  2. Observe that the example doesn't work
  • Version: 0.4.2
  • Platform: Browserstack + Edge 15

Can't observe a React.Suspense element

Expected behavior

Providing a React.Suspense as children to the Observer component renders it just fine

Current behavior

Getting the "ReactIntersectionObserver: Can't find DOM node in the provided children. Make sure to render at least one DOM node in the tree." error when loading a lazily-loaded using React.Suspense and React.Lazy

Steps to reproduce

  1. Simply provide a Componente wrapped in a React.Suspense to the Observer component and it should break as soon as it becomes visible on screen

You can check the example on this sandbox: https://codesandbox.io/s/intersection-observer-suspense-lazy-owmrj?
Not that the Component renders normally, then the lazily-loaded loaded version of it loads as well, and finally, the same component wrapped by the Observer breaks unless we wrap it (lines 22-24) in a div.

Context (environment)

We have a few components on a heavily accessed page that rely on lazy-loading to boost performance. We started noticing a few weeks ago that even after running the unobserve call for them after getting in view was still making the state changes (making the components unmount and mount again).
After updating the lib to v1.2.0 we started getting that "No DOM node" error for the components that used React.Suspense.

  • Version: 1.2.0 (intersection-observer at 0.10.0)
  • Platform: Darwin raphael-lupchinski-mbp13-C02TF12ZFVH4.local 16.7.0 Darwin Kernel Version 16.7.0: Sun Jun 2 20:26:31 PDT 2019; root:xnu-3789.73.50~1/RELEASE_X86_64 x86_64

Need entry and exit hooks

Description
We need entry and exit hooks, so that we can easily add callbacks for entry or exit of the target element

Example
We want to have an entry event when the target element appears on view port and an exit event when it leaves view port.
As the library support array of threshold values, we can find the min value from that threshold and check if target element's intersection ratio is greater than or less than min threshold value.

Why this feature would be useful to this project
End user can simply use entry and exit hooks if he wants to do something on entry or exit of target element. Entry and Exit callbacks can be non mandatory to make it backward compatible.

getpooled function is unexpected

const Child = () => {
  const [ setRef ] = useIntersectionObserver(() => {}, { root: document.body });

  return <div ref={setRef}></div>
}

function App() {
  return (
    <div className="App">
      <Child />
      <Child />
    </div>
  );
}

Expected behavior

the observer instance create in first child would be reuse in second child

Current behavior

two child create two observer instance

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Repository problems

These problems occurred while renovating this repository.

  • WARN: Base branch does not exist - skipping

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • chore(deps): update dependency @babel/core to v7.20.12 (master)
  • chore(deps): update storybook monorepo to v6.5.15 (master) (@storybook/addon-actions, @storybook/react)
  • 🔐 Create all rate-limited PRs at once 🔐

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

github-actions
.github/workflows/build.yml
  • actions/checkout v3
  • actions/setup-node v3
  • actions/cache v3
  • codecov/codecov-action v2
  • actions/checkout v3
  • actions/setup-node v3
  • actions/cache v3
  • JamesIves/github-pages-deploy-action v4.2.5
  • actions/checkout v3
  • actions/setup-node v3
  • actions/cache v3
npm
package.json
  • @babel/core 7.20.2
  • @babel/preset-typescript 7.18.6
  • @researchgate/babel-preset 2.0.14
  • @researchgate/spire-config 7.0.0
  • @storybook/addon-actions 6.5.13
  • @storybook/addon-knobs 6.4.0
  • @storybook/addon-options 5.3.21
  • @storybook/react 6.5.13
  • @testing-library/react-hooks 7.0.2
  • @types/jest 27.5.2
  • @types/react 17.0.52
  • @types/react-dom 17.0.18
  • @typescript-eslint/eslint-plugin 4.33.0
  • @typescript-eslint/parser 4.33.0
  • babel-loader 8.3.0
  • intersection-observer 0.12.2
  • npm-run-all 4.1.5
  • prop-types 15.8.1
  • react 17.0.2
  • react-dom 17.0.2
  • react-test-renderer 17.0.2
  • spire 4.1.2
  • spire-plugin-semantic-release 4.1.0
  • storybook-readme 5.0.9
  • typescript 4.0.5
  • react ^16.3.2 || ^17.0.0
  • react-dom ^16.3.2 || ^17.0.0

  • Check this box to trigger a request for Renovate to run again on this repository

ReferenceError: Can't find variable: IntersectionObserver when testing on Safari Version 11.0.3

Expected behavior

Using Observer in the app should not report any errors inside node_modules. The current usage on our side works on chrome, firefox and other browsers. Expected behaviour is to have Observer work in Safari the way it is working on other browsers.

Current behavior

On safari and Safari only:

ReferenceError: Can't find variable: IntersectionObserver
observe
node_modules/react-intersection-observer/dist/react-intersection-observer.esm.js:102
99 | var observerInstance = observerId ? OBSERVER_MAP.get(observerId) : null;
100 |
101 | if (!observerInstance) {

102 | observerInstance = new IntersectionObserver(onChange, options);
| ^ 103 | if (observerId) OBSERVER_MAP.set(observerId, observerInstance);
104 | }
105 |

Steps to reproduce

  1. Go to https://greenrush-spa.herokuapp.com/menu on Safari
  2. Go to same link on Chrome and other browsers

Context (environment)

Can't find variable: IntersectionObserver when testing on Safari Version 11.0.3
ReferenceError: Can't find variable: IntersectionObserver
observe
node_modules/react-intersection-observer/dist/react-intersection-observer.esm.js:102
99 | var observerInstance = observerId ? OBSERVER_MAP.get(observerId) : null;
100 |
101 | if (!observerInstance) {

102 | observerInstance = new IntersectionObserver(onChange, options);
| ^ 103 | if (observerId) OBSERVER_MAP.set(observerId, observerInstance);
104 | }
105 |

  • Version: 6.2.3
  • Platform: Mac OS High Sierra 10.13.3

Class Wrapper for Stateless Functional Components

Hello! I'm running into an issue with observing stateless functional components (because they can't be given refs), and a potential solution I've found is wrapping the component in a class component that does nothing but returns the child:

class SFCWrapper extends React.Component {
  render() {
    return React.Children.only(this.props.children);
  }
}

This seems to handle the ref, but I'm wondering if this is a viable solution, or will this cause problems I'm not seeing?

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Error type: undefined. Note: this is a nested preset so please contact the preset author if you are unable to fix it yourself.

Gracefully handle environments without IntersectionObserver

This module assumes IntersectionObserver is available as a global - the polyfill is actually required. However, there may be situations where we want to do progressive enhancement and only observe elements if it's available.

I was wondering if this has already been discussed and if we could gracefully fail if IntersectionObserver doesn't exist instead of throwing.

Expected behavior

Exception not thrown. Page scrolls as normal without any callbacks being triggered (as if <Observer> wasn't there).

Current behavior

Exception is thrown (IntersectionObserver is not declared).

Steps to reproduce

Use <Observer> on Safari/iOS.

Sorry, opened accidentally, pls delete.

Thanks for taking the time to file an issue with us.
Please note that this issue template is used ONLY for reporting bugs.

If you have an issue that isn’t a bug, please follow the steps listed in the Contributing.
Thanks!

Expected behavior

Current behavior

Steps to reproduce

Context (environment)

  • Version:
  • Platform:

server side

Thanks for taking the time to file an issue with us.
Please note that this issue template is used ONLY for reporting bugs.

If you have an issue that isn’t a bug, please follow the steps listed in the Contributing.
Thanks!

Expected behavior

Current behavior

Steps to reproduce

Context (environment)

  • Version:
  • Platform:

`process` is undefined

We are utilizing the react-intersection-observer.umd.min.js for a project we are working on.

Expected behavior

When the browser loads the react-intersection-observer module, it should load without errors.

Current behavior

When we import the react-intersection-observer module, it errors out with the following error message -
process is undefined. Digging through the code, we noticed that even on distribution version of the builds, we are trying to check if process.env.NODE_ENV == "production". Since we are running on the browser, it makes sense why Node's process will be undefined.

Steps to reproduce

  1. npm install react-intersection-observer
  2. Create a small app that uses react-intersection-observer
  3. Try to load the react-intersection-observer module through UMD via requirejs
  4. Look at the browser console.

Context (environment)

We are not using webpack. We have our own custom build tooling.

  • Version: 6.3.1
  • Platform: Windows 10 64 Bit

Handle null children exception

Thanks for taking the time to file an issue with us.
Please note that this issue template is used ONLY for reporting bugs.

If you have an issue that isn’t a bug, please follow the steps listed in the Contributing.
Thanks!

Expected behavior

If you pass as a child a React Component which could be null, this causes an unexpected TypeError.

Current behavior

Observer is passed the null instance and a TypeError occurs

Steps to reproduce

  1. Create an IntersectionObserver as a HOC and pass in a React Component (Class or SFC, returning null) as the only child
  2. Observe that the observer attempts to attach to whatever the React Component returns;
    image

Context (environment)

This got past our CI pipeline because TypeScript (correctly) allows null as a valid return type from React.SFC or React.ReactNode. I will try to take a look and provide my own solution, but please also advise if this is due to a limitation within React or otherwise.

  • Version: "0.7.3"
  • Platform: OSX Debian

Doesn't work as expected. All Change events fire upon start. Never again even after scroll

Expected behavior

OnChange fired dynamically when elements go in and out of viewport.

Current behavior

OnChange is fired immediately for ALL components, the intersect property of the event is correct, but those who get false never have an event when I scroll them into the viewport. No events are ever fired for any components after the initial firing of the event for ALL components, intersecting or not.

Steps to reproduce

<Observer onChange={this.onIntersectionChange}>
   <Content>
       {text}
    <Content>
</Observer>
 onIntersectionChange = (event, unobserve) => {
        if (event.isIntersecting) {
           // unobserve();
        }
        console.log("in view port: " + event.isIntersecting );
    };

Context (environment)

React 16.12.0
Chrome (latet)

All Storybook examples are busted

Expected behavior

All examples at https://researchgate.github.io/react-intersection-observer/ should load without errors.

Current behavior

There's a TypeError that prevents any of the examples from loading properly.

Cannot read property 'theme' of undefined

Screen Shot 2020-02-05 at 1 39 30 PM

Steps to reproduce

  1. Go to https://researchgate.github.io/react-intersection-observer/

or

  1. Clone this repo
  2. Run yarn
  3. Run yarn storybook

Context (environment)

This appears to have been caused by a package update. There are a couple of relevant issues in the storybook-readme repo: tuchk4/storybook-readme#221 and tuchk4/storybook-readme#220

ViewableMonitor TypeScript conversion not working

Expected behavior

The ViewableMonitor example to be portable to typescript

Current behavior

I get the following error when attempting to use it in TypeScript

Cannot invoke an expression whose type lacks a call signature. Type 'ReactElement<any> | (string & ReactElement<any>) | (number & ReactElement<any>) | (false & ReactElement<any>) | (true & ReactElement<any>) | (ReactNodeArray & ReactElement<any>) | (ReactPortal & ReactElement<...>)' has no compatible call signatures.

Steps to reproduce

Here is my TypeScript conversion

import Observer from "@researchgate/react-intersection-observer";
import * as React from "react";

interface ViewableMonitorState {
  isIntersecting: boolean;
}
interface ViewableMonitorProps {
  children: React.ReactElement<any>;
  mount: any;
}

interface ViewableEntry {
  isIntersecting: boolean;
}

export default class ViewableMonitor extends React.Component<
  ViewableMonitorProps,
  ViewableMonitorState
> {
  public state = {
    isIntersecting: false
  };

  public render() {
    const { children, mount: Tag, ...rest } = this.props;
    if (!children) {
      return;
    }

    let element = children(this.state.isIntersecting);

    if (Tag) {
      element = <Tag>{element}</Tag>;
    }

    return (
      <Observer {...rest} onChange={this.handleChange}>
        {element}
      </Observer>
    );
  }

  private handleChange = (entry: ViewableEntry) => {
    this.setState({ isIntersecting: entry.isIntersecting });
  };
}

Context (environment)

Trying to get the ViewableMonitor to work in TypeScript

  • Version: 0.7.3
  • Platform: Darwin ajrichbook.local 18.0.0 Darwin Kernel Version 18.0.0: Wed Aug 22 20:13:40 PDT 2018; root:xnu-4903.201.2~1/RELEASE_X86_64 x86_64

Positive rootMargin ignored on horizontal axis

Hello ✌️

Expected behavior

When using positive rootMargin on the right edge, the root element's bounding box should grow in that direction.

Current behavior

The root element's bounding box acts like there are no margins applied.

Context

  • I am using this component to lazy load various scripts on the horizontal axis.
  • The root is the document's viewport (I didn't specify anything actually).
  • This works fine on the vertical axis, or with margins <= 0.
  • No iFrame involved (I've read the issues with iFrames).

Code

I use it in many cases, one of them being a classic infinite scroll pattern, such as (simplified) :

<MyHorizontalList>
  <MyElements />
  <Observer rootMargin="0px 1000px 0px 0px" onChange={this.handleObserverEvent}>
    <MyTrigger />
  </Observer>
</MyHorizontalList>

Screenshot

The most intriguing thing is that the rootBounds has the added margin on the right edge and I think it should intersect according to what I logged 🤔

screenshot 2018-12-18 at 15 30 29

I used 1Mpx margin just to make the example easier, but the behaviour is identical with regular margins.

Thanks !

Version: 0.7.4
Platform: Chrome 71 on macOS (Mojave)

Missing ajv peer dependency warning

Expected behavior

Installing 0.6.1 returns no warnings

Current behavior

λ npm install @researchgate/[email protected]
npm WARN [email protected] requires a peer of ajv@^6.0.0 but none is installed. You must install peer dependencies yourself.

+ @researchgate/[email protected]
updated 1 package in 23.226s

Steps to reproduce

  1. Use 0.6.0 in your app
  2. npm install @researchgate/[email protected]

Context (environment)

  • Version: from 0.6.0 to 0.6.1 on NPM 5.8.0
  • Platform:
    Darwin Chrono.local 17.5.0 Darwin Kernel Version 17.5.0: Mon Mar 5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64 x86_64

"Code review"

Hullo!

This isn't a real issue; instead I thought I'd drop a line since I used this project's code as a basis when implementing Intersection Observer into an older existing lib where I didn't want to have some of the baggage that this project currently has (or had), and I also wanted to do some minor changes to terminology that this project most likely doesn't want to include.

I think this project's implementation of Intersection Observer API for React is the best on npm, and I also think this project is now very close to version 1.0.0 as the Symbol issue was just removed. The weaknesses, from my point of view as an author who might want to use this project in their lib, are mostly in dependencies and use of modern code syntax that results into verbose results (as long as we need to keep babelizing and polyfilling).

I think it is ideal for any component that is used as a basis for other components to have as little dependency and as little unnecessary abstraction as possible. So, from this perspective here are some things that I changed when I refactored this project as part of react-lazy.

IntersectionObserverContainer.js

This goes a lot into opionated side of things, but I couldn't see any purpose for making IntersectionObserverContainer a class. Essentially all the methods are static functions and nothing is self-contained into the class itself. Thus I changed the file into intersectionObserver.js, removed class and renamed the functions to be a bit more verbose.

The advantage in this is reduced final code size. And there is also a little bit less confusion as well since IntersectionObserverContainer is not a React component, which one might first assume it to be.

The callback method

I had two thoughts related to the callback:

  1. create method receives it, but ignores it's existance when calling getPooled method, yet it is required for new IntersectionObserver.
  2. it feels a bit odd to find the callback method defined in IntersectionObserver component file since it relies more on internal mechanics of IntersectionObserverContainer file than what can be found in the component.

The first point introduces a possible future bug as callback is not checked against. Of course the current implementation always has the same callback, but the interface gives an impression that one could change it.

For the second point I think the callback method becomes easier to understand when it is moved to the other file as all the related Map and Set context is there.

Abstraction

Doing these changes would reduce code size.

  • IntersectionObserverContainer.clear() and IntersectionObserverContainer.count() are only used for tests, simple to just remove as storage is exposed anyway
  • compareObserverProps method in the component is easy to inline
  • reobserve method in the component is used only once, easy to inline
  • isDOMTypeElement is used only once, easy to inline

Unnecessary creation of new object in reduce

In component's get options is an unnecessary object spread: you can freely mutate an object you have created for a reducer.

warning is included for production build

I guess this should be inside a `process.env.NODE_ENV !== 'production' block. In the other hand onlyOnce is about to be removed so worth fixing only if it will take a while for the next major release.

Targetting

this.target, this.renderedTarget, this.targetChanged... I know these are very fast to access if compared to calling this.setState, but I can't really avoid the thought that the code could be simpler with setState.

PureComponent?

Current target management prevents using this. With setState this would become a viable solution to the shallow props checking. Of course it isn't exactly the same: the shouldComponentUpdate implementation doesn't check the contents of an array, only the reference. Maybe threshold could be a string instead of an array to make things easier?

Final thoughts

For reference, here are links to how I refactored those files:

I did other minor changes as well which might be interesting to see, but there are so many changes (and I don't use semicolons) that diff isn't much of help. Most of the code should seem very familiar as it does implement the exact same logic, but only does it with either slightly faster means or code that results in reduced babelized code size or less dependencies.

And then there is also the addition of viewport (root) and cushion (rootMargin) props, which seem like easier to understand what those props do than what IntersectionObserver options say.

Anyway, hopefully these thoughts are of some value :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.