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

Commit

Permalink
➕ add with-callback-on-change-while
Browse files Browse the repository at this point in the history
  • Loading branch information
deepsweet committed Aug 21, 2017
1 parent 8fb0371 commit ead00c8
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 0 deletions.
24 changes: 24 additions & 0 deletions packages/with-callback-on-change-while/demo/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable no-console */
import React from 'react';
import { compose, withState, withHandlers } from 'recompose';

import withCallbackOnChangeWhile from '../src/';

const Demo = ({ count, onButtonClick }) => (
<div>
<h1>{count}</h1>
<button onClick={onButtonClick}>increment</button>
</div>
);

export default compose(
withState('count', 'setCount', 0),
withHandlers({
onButtonClick: ({ setCount, count }) => () => setCount(count + 1)
}),
withCallbackOnChangeWhile(
'count',
({ count }) => count <= 5,
({ count }) => console.log(count)
)
)(Demo);
32 changes: 32 additions & 0 deletions packages/with-callback-on-change-while/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@hocs/with-callback-on-change-while",
"library": "withCallbackOnChangeWhile",
"version": "0.1.0",
"description": "Invokes a callback on prop change while condition is true as a HOC for React",
"keywords": [
"react",
"hoc",
"recompose",
"callback"
],
"main": "lib/index.js",
"module": "es/index.js",
"files": [
"dist/",
"es/",
"lib/"
],
"repository": "deepsweet/hocs",
"author": "Kir Belevich <kir@belevi.ch> (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"
}
}
50 changes: 50 additions & 0 deletions packages/with-callback-on-change-while/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# :bell: with-callback-on-change-while

[![npm](https://img.shields.io/npm/v/@hocs/with-callback-on-change-while.svg?style=flat-square)](https://www.npmjs.com/package/@hocs/with-callback-on-change-while) [![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-callback-on-change-while&style=flat-square)](https://david-dm.org/deepsweet/hocs?path=packages/with-callback-on-change-while)

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

Invokes a callback on prop change while condition is true, useful to decouple side effects in a declarative way.

## Install

```
yarn add recompose @hocs/with-callback-on-change-while
```

## Usage

```js
withCallbackOnChangeWhile(
propName: string,
shouldCall: (props: Object) => boolean,
callback: (props: Object) => void
): HigherOrderComponent
```

```js
import React from 'react';
import { compose, withState, withHandlers } from 'recompose';
import withCallbackOnChangeWhile from '@hocs/with-callback-on-change-while';

const Demo = ({ count, onButtonClick }) => (
<div>
<h1>{count}</h1>
<button onClick={onButtonClick}>increment</button>
</div>
);

export default compose(
withState('count', 'setCount', 0),
withHandlers({
onButtonClick: ({ setCount, count }) => () => setCount(count + 1)
}),
withCallbackOnChangeWhile(
'count',
({ count }) => count <= 5,
({ count }) => console.log(count)
)
)(Demo);
```

:tv: [Check out live demo](https://www.webpackbin.com/bins/-Ks4uVbBL6E_mMvepVRa).
31 changes: 31 additions & 0 deletions packages/with-callback-on-change-while/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Component } from 'react';
import { createEagerFactory, setDisplayName, wrapDisplayName } from 'recompose';

const withCallbackOnChangeWhile = (propName, shouldCall, callback) => (Target) => {
const factory = createEagerFactory(Target);

class WithCallbackOnChangeWhile extends Component {
componentWillReceiveProps(nextProps) {
if (
this.props[propName] !== nextProps[propName] &&
shouldCall(nextProps) === true
) {
callback(nextProps);
}
}

render() {
return factory(this.props);
}
}

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

return WithCallbackOnChangeWhile;
};

export default withCallbackOnChangeWhile;
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`withCallbackOnChangeWhile display name should not wrap display name in production env 1`] = `<WithCallbackOnChangeWhile />`;

exports[`withCallbackOnChangeWhile display name should wrap display name in non-production env 1`] = `
<withCallbackOnChangeWhile(Target)>
<Target />
</withCallbackOnChangeWhile(Target)>
`;

exports[`withCallbackOnChangeWhile should invoke a callback on prop change and match condition 1`] = `Array []`;

exports[`withCallbackOnChangeWhile should invoke a callback on prop change and match condition 2`] = `Array []`;

exports[`withCallbackOnChangeWhile should invoke a callback on prop change and match condition 3`] = `
Array [
Array [
Object {
"a": 11,
"b": 2,
"c": 3,
},
],
]
`;

exports[`withCallbackOnChangeWhile should invoke a callback on prop change and match condition 4`] = `
Array [
Array [
Object {
"a": 11,
"b": 2,
"c": 3,
},
],
]
`;

exports[`withCallbackOnChangeWhile should invoke a callback on prop change and match condition 5`] = `
Array [
Array [
Object {
"a": 11,
"b": 2,
"c": 3,
},
],
Array [
Object {
"a": 111,
"b": 2,
"c": 3,
},
],
]
`;

exports[`withCallbackOnChangeWhile should invoke a callback on prop change and match condition 6`] = `
Array [
Array [
Object {
"a": 11,
"b": 2,
"c": 3,
},
],
]
`;

exports[`withCallbackOnChangeWhile should no-op if prop is the same 1`] = `Array []`;

exports[`withCallbackOnChangeWhile should no-op if prop is the same 2`] = `Array []`;

exports[`withCallbackOnChangeWhile should no-op if prop is the same 3`] = `Array []`;

exports[`withCallbackOnChangeWhile should no-op if prop is the same 4`] = `Array []`;

exports[`withCallbackOnChangeWhile should pass props through 1`] = `
<withCallbackOnChangeWhile(Target)
a={1}
b={2}
c={3}
>
<Target
a={1}
b={2}
c={3}
/>
</withCallbackOnChangeWhile(Target)>
`;
88 changes: 88 additions & 0 deletions packages/with-callback-on-change-while/test/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import { mount } from 'enzyme';

import withCallbackOnChangeWhile from '../src/';

const Target = () => null;

describe('withCallbackOnChangeWhile', () => {
it('should pass props through', () => {
const EnhancedTarget = withCallbackOnChangeWhile()(Target);
const wrapper = mount(
<EnhancedTarget a={1} b={2} c={3}/>
);

expect(wrapper).toMatchSnapshot();
});

it('should invoke a callback on prop change and match condition', () => {
const mockShouldCall = jest.fn(() => false).mockImplementationOnce(() => true);
const mockCallback = jest.fn();
const EnhancedTarget = withCallbackOnChangeWhile(
'a',
mockShouldCall,
mockCallback
)(Target);
const wrapper = mount(
<EnhancedTarget a={1} b={2} c={3}/>
);

expect(mockShouldCall.mock.calls).toMatchSnapshot();
expect(mockCallback.mock.calls).toMatchSnapshot();
wrapper.setProps({ a: 11 });
expect(mockShouldCall.mock.calls).toMatchSnapshot();
expect(mockCallback.mock.calls).toMatchSnapshot();
wrapper.setProps({ a: 111 });
expect(mockShouldCall.mock.calls).toMatchSnapshot();
expect(mockCallback.mock.calls).toMatchSnapshot();
});

it('should no-op if prop is the same', () => {
const mockShouldCall = jest.fn();
const mockCallback = jest.fn();
const EnhancedTarget = withCallbackOnChangeWhile(
'a',
mockShouldCall,
mockCallback
)(Target);
const wrapper = mount(
<EnhancedTarget a={1} b={2} c={3}/>
);

expect(mockShouldCall.mock.calls).toMatchSnapshot();
expect(mockCallback.mock.calls).toMatchSnapshot();
wrapper.setProps({ a: 1 });
expect(mockShouldCall.mock.calls).toMatchSnapshot();
expect(mockCallback.mock.calls).toMatchSnapshot();
});

describe('display name', () => {
const origNodeEnv = process.env.NODE_ENV;

afterAll(() => {
process.env.NODE_ENV = origNodeEnv;
});

it('should wrap display name in non-production env', () => {
process.env.NODE_ENV = 'test';

const EnhancedTarget = withCallbackOnChangeWhile()(Target);
const wrapper = mount(
<EnhancedTarget/>
);

expect(wrapper).toMatchSnapshot();
});

it('should not wrap display name in production env', () => {
process.env.NODE_ENV = 'production';

const EnhancedTarget = withCallbackOnChangeWhile()(Target);
const wrapper = mount(
<EnhancedTarget/>
);

expect(wrapper).toMatchSnapshot();
});
});
});
4 changes: 4 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ Provides safe versions of `setTimeout`, `setInterval`, `requestAnimationFrame` a

Invokes a callback on prop change, useful to decouple side effects in a declarative way.

### :bell: [with-callback-on-change-while](packages/with-callback-on-change-while)

Invokes a callback on prop change while condition is true, useful to decouple side effects in a declarative way.

### :mag: [with-log](packages/with-log)

Injects `console.log` with props or any custom message into render.
Expand Down

0 comments on commit ead00c8

Please sign in to comment.