Skip to content

Commit

Permalink
fix: Remove findDOMNode from libary (#125)
Browse files Browse the repository at this point in the history
  • Loading branch information
roderickhsiao committed Apr 29, 2024
1 parent 04a33f0 commit 147f736
Show file tree
Hide file tree
Showing 17 changed files with 2,441 additions and 1,689 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"tsx": "never"
}
],
"react/function-component-definition": [2, { "namedComponents": "arrow-function" }]
},
"env": {
"jest": true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

strategy:
matrix:
node-version: [16.x, 18.x]
node-version: [18.x, 20.x]

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
Expand Down
6 changes: 5 additions & 1 deletion .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
module.exports = {
stories: ['../src/**/*.stories.tsx'],
addons: ['@storybook/addon-docs', '@storybook/addon-actions'],
addons: [
'@storybook/addon-docs',
'@storybook/addon-actions',
'@storybook/addon-webpack5-compiler-babel'
],
docs: {
autodocs: true
},
Expand Down
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ The HOC preserves `onEnterViewport` and `onLeaveViewport` props as a callback
| Props | Type | Default | Description |
|------------|-----------|---------|-----------------------------------------------------------------------------------|
| inViewport | boolean | false | Whether your component is in the viewport |
| forwardedRef | React ref | | If you are using a functional component, assign this prop as a ref on your component |
| forwardedRef | React ref | | Assign this prop as a ref on your component |
| enterCount | number | | Numbers of times your component has entered the viewport |
| leaveCount | number | | Number of times your component has left the viewport |

_NOTE_: Stateless: Need to add `ref={this.props.forwardedRef}` to your component
_NOTE_: Need to add `ref={this.props.forwardedRef}` to your component

#### Example of a functional component

Expand Down Expand Up @@ -133,9 +133,9 @@ class MySectionBlock extends Component {
}

render() {
const { enterCount, leaveCount } = this.props;
const { enterCount, leaveCount, forwardedRef } = this.props;
return (
<section>
<section ref={forwardedRef}>
<div className="content" style={this.getStyle()}>
<h1>Hello</h1>
<p>{`Enter viewport: ${enterCount} times`}</p>
Expand Down Expand Up @@ -183,10 +183,6 @@ const MySectionBlock = () => {
};
```

## Note

This library is using `ReactDOM.findDOMNode` to access the DOM from a React element. This method is deprecated in `StrictMode`. We will update the package and release a major version when React 17 is out.

## Who is using this component

- [Tinder](https://tinder.com)
20 changes: 12 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-in-viewport",
"version": "1.0.0-alpha.30",
"version": "1.0.0-beta.1",
"description": "Track React component in viewport using Intersection Observer API",
"author": "Roderick Hsiao <roderickhsiao@gmail.com>",
"license": "MIT",
Expand Down Expand Up @@ -36,11 +36,12 @@
"@babel/preset-env": "^7.3.1",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.16.7",
"@storybook/addon-actions": "^7.0.0",
"@storybook/addon-docs": "^7.0.0",
"@storybook/addon-essentials": "^7.0.0",
"@storybook/react": "^7.0.0",
"@storybook/react-webpack5": "^7.0.0",
"@storybook/addon-actions": "^8.0.9",
"@storybook/addon-docs": "^8.0.9",
"@storybook/addon-essentials": "^8.0.9",
"@storybook/addon-webpack5-compiler-babel": "^3.0.3",
"@storybook/react": "^8.0.9",
"@storybook/react-webpack5": "^8.0.9",
"@testing-library/react": "^14.0.0",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/jest": "^29.0.0",
Expand All @@ -67,17 +68,20 @@
"jest-environment-jsdom": "^29.3.1",
"jest-junit": "^15.0.0",
"react": "^18.0.0",
"react-aspect-ratio": "^1.1.6",
"react-aspect-ratio": "^1.1.8",
"react-dom": "^18.0.0",
"react-test-renderer": "^18.0.0",
"storybook": "^7.0.0",
"storybook": "^8.0.9",
"typescript": "^5.0.0",
"webpack": "^5.75.0"
},
"peerDependencies": {
"react": "^16.8.3 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.3 || ^17.0.0 || ^18.0.0"
},
"resolutions": {
"jackspeak": "2.1.1"
},
"dependencies": {
"hoist-non-react-statics": "^3.0.0"
},
Expand Down
27 changes: 5 additions & 22 deletions src/lib/handleViewport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,6 @@ import useInViewport from './useInViewport';

import { noop, defaultOptions, defaultConfig } from './constants';

const isFunctionalComponent = (Component: React.ElementType) => {
return (
typeof Component === 'function'
&& !(Component.prototype && Component.prototype.render)
);
};

const isReactComponent = (Component: React.ComponentClass) => {
return Component.prototype && Component.prototype.isReactComponent;
};

function handleViewport<
TElement extends HTMLElement,
TProps extends InjectedViewportProps<TElement>,
Expand All @@ -34,20 +23,16 @@ function handleViewport<
const refProps = {
forwardedRef: ref,
// pass both ref/forwardedRef for class component for backward compatibility
...(isReactComponent(TargetComponent as React.ComponentClass<TProps>)
&& !isFunctionalComponent(TargetComponent)
? { ref }
: {}),
};
return <TargetComponent {...props} {...refProps} />;
});

function InViewport({
const InViewport = ({
onEnterViewport = noop,
onLeaveViewport = noop,
...restProps
}: Omit<TProps, keyof InjectedViewportProps<TElement>> & CallbackProps) {
const node = useRef<TElement>();
}: Omit<TProps, keyof InjectedViewportProps<TElement>> & CallbackProps) => {
const node = useRef<TElement>(null);
const { inViewport, enterCount, leaveCount } = useInViewport(
node,
options,
Expand All @@ -65,10 +50,8 @@ function handleViewport<
leaveCount,
} as React.PropsWithoutRef<TProps>;

return (
<ForwardedRefComponent {...props} ref={node} />
);
}
return <ForwardedRefComponent {...props} ref={node} />;
};

const name = (TargetComponent as React.FC).displayName
|| (TargetComponent as React.FC).name
Expand Down
21 changes: 9 additions & 12 deletions src/lib/useInViewport.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import React, {
useEffect, useRef, useState,
} from 'react';
import { findDOMNode } from 'react-dom';
import React, { useEffect, useRef, useState } from 'react';

import { defaultOptions, defaultConfig, defaultProps } from './constants';

Expand All @@ -10,11 +7,11 @@ import type { Config, CallbackProps, Options } from './types';
const useInViewport = (
target: React.RefObject<HTMLElement>,
options: Options = defaultOptions,
config : Config = defaultConfig,
config: Config = defaultConfig,
props: CallbackProps = defaultProps,
) => {
const { onEnterViewport, onLeaveViewport } = props;
const [, forceUpdate] = useState();
const [, forceUpdate] = useState<boolean>();

const observer = useRef<IntersectionObserver>();

Expand All @@ -27,7 +24,7 @@ const useInViewport = (
function startObserver({ observerRef }) {
const targetRef = target.current;
if (targetRef) {
const node = findDOMNode(targetRef);
const node = targetRef;
if (node) {
observerRef?.observe(node);
}
Expand All @@ -37,7 +34,7 @@ const useInViewport = (
function stopObserver({ observerRef }) {
const targetRef = target.current;
if (targetRef) {
const node = findDOMNode(targetRef);
const node = targetRef;
if (node) {
observerRef?.unobserve(node);
}
Expand All @@ -47,8 +44,8 @@ const useInViewport = (
observer.current = null;
}

function handleIntersection(entries) {
const entry = entries[0] || {};
const handleIntersection: IntersectionObserverCallback = (entries) => {
const entry = entries[0] || {} as IntersectionObserverEntry;
const { isIntersecting, intersectionRatio } = entry;
const isInViewport = typeof isIntersecting !== 'undefined'
? isIntersecting
Expand All @@ -69,14 +66,14 @@ const useInViewport = (
intersected.current = false;
onLeaveViewport?.();
if (config.disconnectOnLeave && observer.current) {
// disconnect obsever on leave
// disconnect observer on leave
observer.current.disconnect();
}
leaveCountRef.current += 1;
inViewportRef.current = isInViewport;
forceUpdate(isInViewport);
}
}
};

function initIntersectionObserver({ observerRef }) {
if (!observerRef) {
Expand Down
12 changes: 6 additions & 6 deletions src/stories/chapters/enterCallback.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import handleViewport, { customProps } from '../../index'; // eslint-disable-lin
import { Block, PageTitle, Spacer } from '../common/themeComponent';

const ViewportBlock = handleViewport(Block, {}, { disconnectOnLeave: false });
function CustomAnchor({ forwardedRef, inViewport, ...restProps }) {
const CustomAnchor = ({ forwardedRef, inViewport, ...restProps }) => {
const text = inViewport ? 'Link (in viewport)' : 'Link (not in viewport)';
return (
<a
Expand All @@ -17,7 +17,7 @@ function CustomAnchor({ forwardedRef, inViewport, ...restProps }) {
{text}
</a>
);
}
};
const ViewportAnchor = handleViewport(
CustomAnchor,
{},
Expand All @@ -40,20 +40,20 @@ export default {
],
};

export function ClassBaseComponent() {
export const ClassBaseComponent = () => {
return (
<ViewportBlock
onEnterViewport={() => action('callback')('onEnterViewport')}
onLeaveViewport={() => action('callback')('onLeaveViewport')}
/>
);
}
};

export function FunctionalComponent() {
export const FunctionalComponent = () => {
return (
<ViewportAnchor
onEnterViewport={() => action('callback')('onEnterViewport')}
onLeaveViewport={() => action('callback')('onLeaveViewport')}
/>
);
}
};
16 changes: 8 additions & 8 deletions src/stories/chapters/lazyMedia.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default {
],
};

export function ClassComponentLazyloadImage() {
export const ClassComponentLazyloadImage = () => {
const imageArray = [
{
src: 'https://s-media-cache-ak0.pinimg.com/originals/cf/31/83/cf31837a53dc1cdb13880ac38c66d70d.jpg',
Expand All @@ -43,9 +43,9 @@ export function ClassComponentLazyloadImage() {
))}
/>
);
}
};

export function ClassComponentLazyloadIframe() {
export const ClassComponentLazyloadIframe = () => {
const iframeArray = [
{
src: 'https://www.youtube.com/embed/hTcBnxxuAls',
Expand All @@ -68,9 +68,9 @@ export function ClassComponentLazyloadIframe() {
))}
/>
);
}
};

export function FunctionalComponentLazyloadImage() {
export const FunctionalComponentLazyloadImage = () => {
const imageArray = [
{
src: 'https://s-media-cache-ak0.pinimg.com/originals/cf/31/83/cf31837a53dc1cdb13880ac38c66d70d.jpg',
Expand All @@ -97,9 +97,9 @@ export function FunctionalComponentLazyloadImage() {
))}
/>
);
}
};

export function FunctionalComponentLazyloadIframe() {
export const FunctionalComponentLazyloadIframe = () => {
const iframeArray = [
{
src: 'https://www.youtube.com/embed/hTcBnxxuAls',
Expand All @@ -126,4 +126,4 @@ export function FunctionalComponentLazyloadIframe() {
))}
/>
);
}
};
4 changes: 2 additions & 2 deletions src/stories/chapters/transition.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export default {
],
};

export function ClassComponentTransition() {
export const ClassComponentTransition = () => {
return <SectionWithTransition />;
}
};
8 changes: 3 additions & 5 deletions src/stories/common/Iframe.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { PureComponent } from 'react';
import { PureComponent, RefObject } from 'react';
import { AspectRatio } from 'react-aspect-ratio';

import { handleViewport } from '../../index';
Expand All @@ -9,10 +9,7 @@ type IframeProps = InjectedViewportProps & {
ratio: string;
};

class Iframe extends PureComponent<
IframeProps,
{ loaded: boolean }
> {
class Iframe extends PureComponent<IframeProps, { loaded: boolean }> {
constructor(props) {
super(props);
this.state = {
Expand Down Expand Up @@ -43,6 +40,7 @@ IframeProps,
<AspectRatio
ratio={ratio}
style={{ marginBottom: '200px', backgroundColor: 'rgba(0,0,0,.12)' }}
ref={this.props.forwardedRef as RefObject<HTMLDivElement>}
>
<Component {...props} />
</AspectRatio>
Expand Down
4 changes: 2 additions & 2 deletions src/stories/common/IframeFunctional.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type IframeFunctionalProps = InjectedViewportProps<HTMLDivElement> & {
ratio: string;
};

function IframeFunctional(props: IframeFunctionalProps) {
const IframeFunctional = (props: IframeFunctionalProps) => {
const {
inViewport, src, ratio, forwardedRef,
} = props;
Expand Down Expand Up @@ -38,7 +38,7 @@ function IframeFunctional(props: IframeFunctionalProps) {
<Component {...componentProps} />
</AspectRatio>
);
}
};

const LazyIframe = handleViewport(memo(IframeFunctional), {}, { disconnectOnLeave: true });
export default LazyIframe;

0 comments on commit 147f736

Please sign in to comment.