Skip to content

Commit

Permalink
feat(*): add hooks as a replacement for HOCs
Browse files Browse the repository at this point in the history
  • Loading branch information
garthenweb committed Oct 27, 2018
1 parent 96c3d22 commit 9853706
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 312 deletions.
11 changes: 11 additions & 0 deletions examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ViewportProvider,
ObserveViewport,
connectViewport,
useScroll,
} from '../lib/index';
import StickyScrollUp from './StickyScrollUp';
import Sticky from './Sticky';
Expand All @@ -18,10 +19,20 @@ const ViewportHeader = connectViewport({ omit: ['scroll'] })<{ a: string }>(
<header className="header">
Viewport: {dimensions.width}x{dimensions.height}
{a}
<DisplayScroll />
</header>
),
);

const DisplayScroll = () => {
const { x, y } = useScroll();
return (
<>
x: {x}, y: {y}
</>
);
};

class Example extends React.PureComponent<{}, { disabled: boolean }> {
private container1: React.RefObject<any>;
private container2: React.RefObject<any>;
Expand Down
26 changes: 6 additions & 20 deletions lib/ObserveViewport.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import raf from 'raf';

import { Consumer } from './ViewportProvider';
import { ViewportContext } from './ViewportProvider';
import {
createInitDimensionsState,
createInitScrollState,
Expand All @@ -13,6 +13,7 @@ import {
TViewportChangeHandler,
IViewportChangeOptions,
} from './types';
import { warnNoContextAvailable } from './utils';

export interface IChildProps {
scroll: IScroll | null;
Expand Down Expand Up @@ -113,24 +114,7 @@ export default class ObserveViewport extends React.Component<IProps, IState> {
hasRootProviderAsParent,
}: IContext): React.ReactNode => {
if (!hasRootProviderAsParent) {
if (process.env.NODE_ENV !== 'production') {
console.warn(
`react-viewport-utils: <ObserveViewport> component is not able to connect to a <ViewportProvider>. Therefore it cannot detect updates from the viewport and will not work as expected. To resolve this issue please add a <ViewportProvider> as a parent of the <ObserveViewport> component, e.g. directly in the ReactDOM.render call:
import * as ReactDOM from 'react-dom';
import { ViewportProvider, ObserveViewport } from 'react-viewport-utils';
ReactDOM.render(
<ViewportProvider>
<main role="main">
<ObserveViewport>
{({ scroll, dimensions }) => ...}
</ObserveViewport>
</main>
</ViewportProvider>,
document.getElementById('root')
);`,
);
}
warnNoContextAvailable('ObserveViewport');
return null;
}

Expand Down Expand Up @@ -166,7 +150,9 @@ ReactDOM.render(
const { children } = this.props;
return (
<React.Fragment>
<Consumer>{this.registerViewportListeners}</Consumer>
<ViewportContext.Consumer>
{this.registerViewportListeners}
</ViewportContext.Consumer>
{typeof children === 'function' && children(this.state)}
</React.Fragment>
);
Expand Down
4 changes: 1 addition & 3 deletions lib/ViewportProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface IListener extends IViewportChangeOptions {
handler: TViewportChangeHandler;
}

const ViewportContext = React.createContext({
export const ViewportContext = React.createContext({
removeViewportChangeListener: (handler: TViewportChangeHandler) => {},
addViewportChangeListener: (
handler: TViewportChangeHandler,
Expand All @@ -23,8 +23,6 @@ const ViewportContext = React.createContext({
version: VERSION,
});

export const Consumer = ViewportContext.Consumer;

export default class ViewportProvider extends React.PureComponent<
{},
{ hasListeners: boolean }
Expand Down
80 changes: 80 additions & 0 deletions lib/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useContext, useEffect, useState } from 'react';

import { ViewportContext } from './ViewportProvider';
import {
createInitScrollState,
createInitDimensionsState,
} from './ViewportCollector';
import { IViewport, IScroll, IDimensions } from './types';
import { warnNoContextAvailable } from './utils';

interface IFullOptions {
recalculateLayoutBeforeUpdate?: (props: IViewport) => any;
disableScrollUpdates?: boolean;
disableDimensionsUpdates?: boolean;
deferUpdateUntilIdle?: boolean;
}

interface IOptions {
deferUpdateUntilIdle?: boolean;
recalculateLayoutBeforeUpdate?: (props: IViewport) => any;
}

type HandleViewportChangeType = (viewport: IViewport) => void;

const useViewportEffect = (
handleViewportChange: HandleViewportChangeType,
options: IFullOptions,
) => {
const {
addViewportChangeListener,
removeViewportChangeListener,
hasRootProviderAsParent,
} = useContext(ViewportContext);

if (!hasRootProviderAsParent) {
warnNoContextAvailable('useViewport');
return;
}

useEffect(
() => {
addViewportChangeListener(handleViewportChange, {
notifyScroll: () => !options.disableScrollUpdates,
notifyDimensions: () => !options.disableDimensionsUpdates,
notifyOnlyWhenIdle: () => Boolean(options.deferUpdateUntilIdle),
recalculateLayoutBeforeUpdate: options.recalculateLayoutBeforeUpdate,
});
return () => removeViewportChangeListener(handleViewportChange);
},
[addViewportChangeListener, removeViewportChangeListener],
);
};

export const useScroll = (options: IOptions = {}): IScroll => {
const { scroll } = useViewport({
disableDimensionsUpdates: true,
...options,
});

return scroll;
};

export const useDimensions = (options: IOptions = {}): IDimensions => {
const { dimensions } = useViewport({
disableScrollUpdates: true,
...options,
});

return dimensions;
};

export const useViewport = (options: IFullOptions = {}): IViewport => {
const [state, setViewport] = useState({
scroll: createInitScrollState(),
viewport: createInitDimensionsState(),
});
useViewportEffect(setViewport, options);

return state;
};
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
default as ObserveBoundingClientRect,
} from './ObserveBoundingClientRect';
export { default as ObserveViewport } from './ObserveViewport';
export { useScroll, useDimensions, useViewport } from './hooks';
export { IRect, IScroll, IDimensions } from './types';

export const VERSION = '__VERSION__';
23 changes: 23 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,26 @@ export const debounceOnUpdate = (
}, delay);
};
};

export const warnNoContextAvailable = (location: string) => {
if (process.env.NODE_ENV === 'production') {
return;
}
const type = location.startsWith('use') ? 'hook' : 'component';
console.warn(
`react-viewport-utils: ${name} ${type} is not able to connect to a <ViewportProvider>. Therefore it cannot detect updates from the viewport and will not work as expected. To resolve this issue please add a <ViewportProvider> as a parent of the <ObserveViewport> component, e.g. directly in the ReactDOM.render call:
import * as ReactDOM from 'react-dom';
import { ViewportProvider, ObserveViewport } from 'react-viewport-utils';
ReactDOM.render(
<ViewportProvider>
<main role="main">
<ObserveViewport>
{({ scroll, dimensions }) => ...}
</ObserveViewport>
</main>
</ViewportProvider>,
document.getElementById('root')
);`,
);
};
Loading

0 comments on commit 9853706

Please sign in to comment.