Skip to content
This repository has been archived by the owner on Jul 8, 2023. It is now read-only.

Commit

Permalink
➕ add with-resize-observer-props
Browse files Browse the repository at this point in the history
  • Loading branch information
deepsweet committed Aug 26, 2017
1 parent e47f5fc commit a72e8ad
Show file tree
Hide file tree
Showing 8 changed files with 653 additions and 0 deletions.
27 changes: 27 additions & 0 deletions packages/with-resize-observer-props/demo/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { compose, withProps } from 'recompose';
import 'resize-observer-polyfill/dist/ResizeObserver.global';

import withResizeObserverProps from '../src/';

const Demo = ({ onRef, hasNarrowWidth, hasLongHeight }) => (
<textarea
readOnly={true}
ref={onRef}
style={{ width: 400, height: 100 }}
value={`resize me!\n${JSON.stringify({ hasNarrowWidth, hasLongHeight })}`}
/>
);

export default compose(
withProps({
myWidthBreakpoint: 500,
myHeightBreakpoint: 300
}),
withResizeObserverProps(
({ myWidthBreakpoint, myHeightBreakpoint }) => ({ width, height }) => ({
hasNarrowWidth: width < myWidthBreakpoint,
hasLongHeight: height >= myHeightBreakpoint
})
)
)(Demo);
39 changes: 39 additions & 0 deletions packages/with-resize-observer-props/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@hocs/with-resize-observer-props",
"library": "withResizeObserverProps",
"version": "0.1.0",
"description": "Resize Observer HOC for React",
"keywords": [
"react",
"hoc",
"recompose",
"resize",
"resizeobserver"
],
"main": "lib/index.js",
"module": "es/index.js",
"files": [
"dist/",
"es/",
"lib/"
],
"repository": "deepsweet/hocs",
"author": "Kir Belevich <[email protected]> (https://github.com/deepsweet)",
"license": {
"type": "MIT",
"url": "https://github.com/deepsweet/hocs/blob/master/license.md"
},
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"react": "^15.6.1",
"recompose": "^0.25.0"
},
"devDependencies": {
"resize-observer-polyfill": "^1.4.2"
},
"dependencies": {
"shallowequal": "^1.0.2"
}
}
79 changes: 79 additions & 0 deletions packages/with-resize-observer-props/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# :left_right_arrow: with-resize-observer-props

[![npm](https://img.shields.io/npm/v/@hocs/with-resize-observer-props.svg?style=flat-square)](https://www.npmjs.com/package/@hocs/with-resize-observer-props) [![ci](https://img.shields.io/travis/deepsweet/hocs/master.svg?style=flat-square)](https://travis-ci.org/deepsweet/hocs) [![coverage](https://img.shields.io/codecov/c/github/deepsweet/hocs/master.svg?style=flat-square)](https://codecov.io/github/deepsweet/hocs) [![deps](https://david-dm.org/deepsweet/hocs.svg?path=packages/with-resize-observer-props&style=flat-square)](https://david-dm.org/deepsweet/hocs?path=packages/with-resize-observer-props)

Part of a [collection](https://github.com/deepsweet/hocs) of Higher-Order Components for React, especially useful with [Recompose](https://github.com/acdlite/recompose).

Dynamically map *component* size and position changes to props using [Resize Observer API](https://github.com/WICG/ResizeObserver) ([Can I use?](https://caniuse.com/#feat=resizeobserver) :see_no_evil:).

## Install

```
yarn add recompose @hocs/with-resize-observer-props
```

## Usage

```js
withResizeObserverProps(
callback: (ownerProps: Object) => (observerState: Object) => Object | void,
onRefHandlerName?: string
): HigherOrderComponent
```

Where:

* `observerState`[`contentRect` object](https://wicg.github.io/ResizeObserver/#dom-resizeobserverentry-contentrect) with `width`, `height`, `top` and `left` properties.
* `onRefHandlerName` – in some cases you might want to change it. `'onRef'` by default.

```js
import React from 'react';
import { compose, withProps } from 'recompose';
import 'resize-observer-polyfill/dist/ResizeObserver.global';
import withResizeObserverProps from '@hocs/with-resize-observer-props';

const Demo = ({ onRef, hasNarrowWidth, hasLongHeight }) => (
<textarea
readOnly={true}
ref={onRef}
style={{ width: 400, height: 100 }}
value={`resize me!\n${JSON.stringify({ hasNarrowWidth, hasLongHeight })}`}
/>
);

export default compose(
withProps({
myWidthBreakpoint: 500,
myHeightBreakpoint: 300
}),
withResizeObserverProps(
({ myWidthBreakpoint, myHeightBreakpoint }) => ({ width, height }) => ({
hasNarrowWidth: width < myWidthBreakpoint,
hasLongHeight: height >= myHeightBreakpoint
})
)
)(Demo);
```

It uses "shallow equal" under the hood to compare computed props so target component will be re-rendered only when it needs to.

In addition, it's possible to not return computed props from a callback at all. It's very usefull if you need just to propagate size changes to "outside" of component without self re-renders, for example to animate width/height with [react-motion](https://github.com/chenglou/react-motion) or similar libs.

```js
withResizeObserverProps(
({ onResize }) => onResize
)
```

:tv: [Check out live demo](https://www.webpackbin.com/bins/-KsUVUj_IHaULBEW0oKx).

## Notes

* You still might need a [polyfill](https://github.com/que-etc/resize-observer-polyfill) – contains many details on why this particular polyfill is just technically amazing.
* "`ref` approach" is used instead of `findDOMNode(this)` because it's just [less evil](https://facebook.github.io/react/docs/refs-and-the-dom.html#exposing-dom-refs-to-parent-components). Also it's more flexible so you can pass it to whatever children you want.
* Target Component will be just passed through on unsupported platforms (i.e. `global.ResizeObserver` is not a function) like IE9, JSDOM (so Jest as well) or with Server-Side Rendering. This means that there will be no boolean props (i.e. `undefined`) which might be expected, but you can take care of it using Recompose [`defaultProps`](https://github.com/acdlite/recompose/blob/master/docs/API.md#defaultprops) HOC if it's really necessary.

## Related

* :left_right_arrow: [with-match-media-props](../with-match-media-props)
* :eyes: [with-intersection-observer-props](../with-intersection-observer-props)
73 changes: 73 additions & 0 deletions packages/with-resize-observer-props/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Component } from 'react';
import { createEagerFactory, setDisplayName, wrapDisplayName } from 'recompose';
import shallowEqual from 'shallowequal';

const isResizeObserverSupported = typeof global.ResizeObserver === 'function';

const withResizeObserverProps = (propsCallback, onRefHandlerName = 'onRef') => (Target) => {
if (!isResizeObserverSupported) {
return Target;
}

const factory = createEagerFactory(Target);

class WithResizeObserverProps extends Component {
constructor(props, context) {
super(props, context);

this.state = {};
this.onObserve = this.onObserve.bind(this);
this.onRef = this.onRef.bind(this);
this.observer = new global.ResizeObserver(this.onObserve);
}

componentDidMount() {
this.observer.observe(this.domNode);
}

componentWillUnmount() {
this.observer.disconnect();
}

onRef(ref) {
this.domNode = ref;

if (typeof this.props[onRefHandlerName] === 'function') {
this.props[onRefHandlerName](ref);
}
}

onObserve(entries) {
const stateCallback = propsCallback(this.props);
const nextState = entries.reduce((result, entry) => ({
...result,
...stateCallback(entry.contentRect)
}), {});

if (
typeof nextState !== 'undefined' &&
shallowEqual(this.state, nextState) === false
) {
this.setState(nextState);
}
}

render() {
return factory({
...this.props,
...this.state,
[onRefHandlerName]: this.onRef
});
}
}

if (process.env.NODE_ENV !== 'production') {
return setDisplayName(
wrapDisplayName(Target, 'withResizeObserverProps')
)(WithResizeObserverProps);
}

return WithResizeObserverProps;
};

export default withResizeObserverProps;
Loading

0 comments on commit a72e8ad

Please sign in to comment.