├── .babelrc
├── .gitignore
├── README.md
├── dist
└── watch-props.js
├── help
└── test-env.js
├── package.json
├── src
└── index.js
├── test
└── index.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-app"],
3 | "plugins": [
4 | "transform-decorators-legacy"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json
3 | .history/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # watch-props
2 | A higher-order component that adds watch props features to the react component
3 |
4 | ### Install
5 | npm i --save watch-props
6 |
7 | ### Usage
8 |
9 | ```js
10 | import React from 'react'
11 | import watchProps from 'watch-props'
12 |
13 | @watchProps
14 | class App extends React.Component{
15 | watch={
16 | id: function(newValue, oldValue){
17 | console.log(newValue, oldValue) //print "100, 1"
18 | }
19 | }
20 | }
21 |
22 | class Home extends React.Component{
23 | state = {
24 | id: 1
25 | }
26 |
27 | componentDidMount(){
28 | setTimeout(()=>{
29 | this.setState({
30 | id: 100
31 | })
32 | },1000)
33 | }
34 |
35 | render(){
36 | return
37 | }
38 | }
39 |
40 |
41 |
42 |
43 | ```
44 |
--------------------------------------------------------------------------------
/dist/watch-props.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory(require("react"));
4 | else if(typeof define === 'function' && define.amd)
5 | define(["react"], factory);
6 | else if(typeof exports === 'object')
7 | exports["watchProps"] = factory(require("react"));
8 | else
9 | root["watchProps"] = factory(root["React"]);
10 | })(window, function(__WEBPACK_EXTERNAL_MODULE__0__) {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 | /******/
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 | /******/
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId]) {
20 | /******/ return installedModules[moduleId].exports;
21 | /******/ }
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ i: moduleId,
25 | /******/ l: false,
26 | /******/ exports: {}
27 | /******/ };
28 | /******/
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 | /******/
32 | /******/ // Flag the module as loaded
33 | /******/ module.l = true;
34 | /******/
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 | /******/
39 | /******/
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 | /******/
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 | /******/
46 | /******/ // define getter function for harmony exports
47 | /******/ __webpack_require__.d = function(exports, name, getter) {
48 | /******/ if(!__webpack_require__.o(exports, name)) {
49 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
50 | /******/ }
51 | /******/ };
52 | /******/
53 | /******/ // define __esModule on exports
54 | /******/ __webpack_require__.r = function(exports) {
55 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
56 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
57 | /******/ }
58 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
59 | /******/ };
60 | /******/
61 | /******/ // create a fake namespace object
62 | /******/ // mode & 1: value is a module id, require it
63 | /******/ // mode & 2: merge all properties of value into the ns
64 | /******/ // mode & 4: return value when already ns object
65 | /******/ // mode & 8|1: behave like require
66 | /******/ __webpack_require__.t = function(value, mode) {
67 | /******/ if(mode & 1) value = __webpack_require__(value);
68 | /******/ if(mode & 8) return value;
69 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
70 | /******/ var ns = Object.create(null);
71 | /******/ __webpack_require__.r(ns);
72 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
73 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
74 | /******/ return ns;
75 | /******/ };
76 | /******/
77 | /******/ // getDefaultExport function for compatibility with non-harmony modules
78 | /******/ __webpack_require__.n = function(module) {
79 | /******/ var getter = module && module.__esModule ?
80 | /******/ function getDefault() { return module['default']; } :
81 | /******/ function getModuleExports() { return module; };
82 | /******/ __webpack_require__.d(getter, 'a', getter);
83 | /******/ return getter;
84 | /******/ };
85 | /******/
86 | /******/ // Object.prototype.hasOwnProperty.call
87 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
88 | /******/
89 | /******/ // __webpack_public_path__
90 | /******/ __webpack_require__.p = "";
91 | /******/
92 | /******/
93 | /******/ // Load entry module and return exports
94 | /******/ return __webpack_require__(__webpack_require__.s = 1);
95 | /******/ })
96 | /************************************************************************/
97 | /******/ ([
98 | /* 0 */
99 | /***/ (function(module, exports) {
100 |
101 | module.exports = __WEBPACK_EXTERNAL_MODULE__0__;
102 |
103 | /***/ }),
104 | /* 1 */
105 | /***/ (function(module, __webpack_exports__, __webpack_require__) {
106 |
107 | "use strict";
108 | __webpack_require__.r(__webpack_exports__);
109 | /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return WatchComponentCreator; });
110 | /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(0);
111 | /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
112 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
113 |
114 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
115 |
116 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
117 |
118 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
119 |
120 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
121 |
122 |
123 |
124 | function getParams(state) {
125 | var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
126 |
127 | var watch = state.__watchObject;
128 | if (watch && (typeof watch === 'undefined' ? 'undefined' : _typeof(watch)) === 'object') {
129 | var params = {};
130 | Object.keys(watch).forEach(function (key) {
131 | params[key] = props[key];
132 | });
133 | return params;
134 | }
135 | }
136 |
137 | function shallowEqual() {
138 | var data1 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
139 | var data2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
140 |
141 | var keys = Object.keys(data1);
142 | for (var i in keys) {
143 | var key = keys[i];
144 | if (data1[key] !== data2[key]) {
145 | return false;
146 | }
147 | }
148 | return true;
149 | }
150 |
151 | function checkReactGt163() {
152 | var versions = react__WEBPACK_IMPORTED_MODULE_0___default.a.version.split('.');
153 | if (versions[0] > 16) return true;
154 | if (versions[0] == 16 && versions[1] >= 3) {
155 | return true;
156 | }
157 | return false;
158 | }
159 |
160 | function handleReactGt163(WatchComponent) {
161 | WatchComponent.prototype.componentDidUpdate = function (prevProps) {
162 | var _this = this;
163 |
164 | var params = this.state.__watchState;
165 | var watchObject = this.state.__watchObject;
166 | if (!watchObject || !params) {
167 | return;
168 | }
169 | Object.keys(watchObject).forEach(function (key) {
170 | if (params[key] !== prevProps[key]) {
171 | if (watchObject[key] && typeof watchObject[key] === 'function') {
172 | watchObject[key].call(_this.child, params[key], prevProps[key]);
173 | }
174 | }
175 | });
176 | };
177 |
178 | WatchComponent.getDerivedStateFromProps = function (nextProps, prevState) {
179 | var params = getParams(prevState, nextProps);
180 | var __watchObject = prevState.__watchObject;
181 | if (!params || !__watchObject) {
182 | return null;
183 | }
184 | if (!prevState.__watchState) {
185 | return {
186 | __watchState: params
187 | };
188 | } else if (!shallowEqual(params, prevState.__watchState)) {
189 | return {
190 | __watchState: params
191 | };
192 | }
193 | return null;
194 | };
195 | }
196 |
197 | function handleReactLt163(WatchComponent) {
198 | WatchComponent.prototype.componentWillReceiveProps = function (nextProps) {
199 | var _this2 = this;
200 |
201 | if (!this.state.__watchObject) return;
202 | var watchObject = this.state.__watchObject;
203 | Object.keys(watchObject).forEach(function (key) {
204 | if (_this2.props[key] !== nextProps[key]) {
205 | watchObject[key].call(_this2.child, nextProps[key], _this2.props[key]);
206 | }
207 | });
208 | };
209 | }
210 |
211 | function WatchComponentCreator(WrappedComponent) {
212 | var WatchComponent = function (_React$PureComponent) {
213 | _inherits(WatchComponent, _React$PureComponent);
214 |
215 | function WatchComponent(props) {
216 | _classCallCheck(this, WatchComponent);
217 |
218 | var _this3 = _possibleConstructorReturn(this, (WatchComponent.__proto__ || Object.getPrototypeOf(WatchComponent)).call(this, props));
219 |
220 | _this3.state = {};
221 | return _this3;
222 | }
223 |
224 | _createClass(WatchComponent, [{
225 | key: 'componentDidMount',
226 | value: function componentDidMount() {
227 | var watch = this.child.watch;
228 | if (watch) {
229 | if (typeof watch === 'function') {
230 | this.setState({
231 | __watchObject: watch.call(this.child)
232 | });
233 | } else if ((typeof watch === 'undefined' ? 'undefined' : _typeof(watch)) === 'object') {
234 | this.setState({
235 | __watchObject: watch
236 | });
237 | }
238 | }
239 | }
240 | }, {
241 | key: 'render',
242 | value: function render() {
243 | var _this4 = this;
244 |
245 | return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(WrappedComponent, Object.assign({ ref: function ref(child) {
246 | return _this4.child = child;
247 | } }, this.props));
248 | }
249 | }]);
250 |
251 | return WatchComponent;
252 | }(react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent);
253 |
254 | if (checkReactGt163()) {
255 | handleReactGt163(WatchComponent);
256 | } else {
257 | handleReactLt163(WatchComponent);
258 | }
259 |
260 | return WatchComponent;
261 | }
262 |
263 | /***/ })
264 | /******/ ]);
265 | });
--------------------------------------------------------------------------------
/help/test-env.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 |
3 | require('core-js/es6/map')
4 | require('core-js/es6/set')
5 | require('raf/polyfill')
6 |
7 |
8 |
9 | global.document = require('jsdom').jsdom('
')
10 | global.window = document.defaultView
11 | global.navigator = window.navigator
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "watch-props",
3 | "version": "0.0.3",
4 | "description": "",
5 | "main": "dist/watch-props.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "scripts": {
10 | "build": "NODE_ENV=production webpack --config webpack.config.js",
11 | "test": "NODE_ENV=test ava --verbose",
12 | "prepublish": "npm run test && npm run build"
13 | },
14 | "pre-commit": [
15 | "test"
16 | ],
17 | "keywords": [],
18 | "author": "",
19 | "license": "ISC",
20 | "dependencies": {
21 | "react": "^16.5.2",
22 | "react-dom": "^16.5.2"
23 | },
24 | "devDependencies": {
25 | "ava": "^0.25.0",
26 | "babel-cli": "^6.26.0",
27 | "babel-loader": "^7.1.5",
28 | "babel-plugin-transform-decorators-legacy": "^1.3.5",
29 | "babel-preset-react-app": "^3.1.0",
30 | "babel-register": "^6.26.0",
31 | "core-js": "^2.5.7",
32 | "enzyme": "^3.7.0",
33 | "enzyme-adapter-react-16": "^1.6.0",
34 | "jsdom": "^7.2.2",
35 | "pre-commit": "^1.2.2",
36 | "prepublish": "^2.2.0",
37 | "prop-types": "^15.6.2",
38 | "raf": "^3.4.0",
39 | "webpack": "^4.20.2",
40 | "webpack-cli": "^3.1.2"
41 | },
42 | "ava": {
43 | "files": [
44 | "test/**/*.js"
45 | ],
46 | "require": [
47 | "./help/test-env.js"
48 | ],
49 | "babel": "inherit"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function getParams (state, props = {}) {
4 | let watch = state.__watchObject;
5 | if (watch && typeof watch === 'object') {
6 | let params = {};
7 | Object.keys (watch).forEach (key => {
8 | params[key] = props[key];
9 | });
10 | return params;
11 | }
12 | }
13 |
14 | function shallowEqual (data1 = {}, data2 = {}) {
15 | let keys = Object.keys (data1);
16 | for (let i in keys) {
17 | let key = keys[i];
18 | if (data1[key] !== data2[key]) {
19 | return false;
20 | }
21 | }
22 | return true;
23 | }
24 |
25 | function checkReactGt163 () {
26 | let versions = React.version.split ('.');
27 | if (versions[0] > 16) return true;
28 | if (versions[0] == 16 && versions[1] >= 3) {
29 | return true;
30 | }
31 | return false;
32 | }
33 |
34 | function handleReactGt163 (WatchComponent) {
35 | WatchComponent.prototype.componentDidUpdate = function (prevProps) {
36 | let params = this.state.__watchState;
37 | let watchObject = this.state.__watchObject;
38 | if (!watchObject || !params) {
39 | return;
40 | }
41 | Object.keys (watchObject).forEach (key => {
42 | if (params[key] !== prevProps[key]) {
43 | if (watchObject[key] && typeof watchObject[key] === 'function') {
44 | watchObject[key].call (this.child, params[key], prevProps[key]);
45 | }
46 | }
47 | });
48 | };
49 |
50 | WatchComponent.getDerivedStateFromProps = function (nextProps, prevState) {
51 | let params = getParams (prevState, nextProps);
52 | let __watchObject = prevState.__watchObject;
53 | if (!params || !__watchObject) {
54 | return null;
55 | }
56 | if (!prevState.__watchState) {
57 | return {
58 | __watchState: params,
59 | };
60 | } else if (!shallowEqual (params, prevState.__watchState)) {
61 | return {
62 | __watchState: params,
63 | };
64 | }
65 | return null;
66 | };
67 | }
68 |
69 | function handleReactLt163(WatchComponent){
70 | WatchComponent.prototype.componentWillReceiveProps = function(nextProps){
71 | if(!this.state.__watchObject) return;
72 | let watchObject = this.state.__watchObject;
73 | Object.keys(watchObject).forEach(key=>{
74 | if(this.props[key] !== nextProps[key]){
75 | watchObject[key].call(this.child, nextProps[key], this.props[key])
76 | }
77 | })
78 | }
79 | }
80 |
81 | export default function WatchComponentCreator (WrappedComponent) {
82 | class WatchComponent extends React.PureComponent {
83 | constructor (props) {
84 | super (props);
85 | this.state = {};
86 | }
87 |
88 | componentDidMount(){
89 | let watch = this.child.watch;
90 | if (watch) {
91 | if (typeof watch === 'function') {
92 | this.setState ({
93 | __watchObject: watch.call (this.child),
94 | });
95 | } else if (typeof watch === 'object') {
96 | this.setState ({
97 | __watchObject: watch,
98 | });
99 | }
100 | }
101 | }
102 |
103 | render () {
104 | return (
105 | (this.child = child)} {...this.props} />
106 | );
107 | }
108 | }
109 |
110 | if (checkReactGt163 ()) {
111 | handleReactGt163 (WatchComponent);
112 | } else {
113 | handleReactLt163 (WatchComponent);
114 | }
115 |
116 | return WatchComponent;
117 | }
118 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import test from 'ava'
2 | import watchProps from '../dist/watch-props'
3 | import React from 'react'
4 | import PropTypes from 'prop-types'
5 |
6 | import { mount, configure } from 'enzyme';
7 | import Adapter from 'enzyme-adapter-react-16';
8 |
9 | configure({ adapter: new Adapter() });
10 |
11 | test.cb('Test React 16 watchProps', t=>{
12 | let num = 0;
13 | class App extends React.Component{
14 | static propTypes = {
15 | x: PropTypes.string
16 | }
17 | state={
18 | a: this.props.x
19 | }
20 |
21 | watch ={
22 | x: function(val, old){
23 | num++;
24 | this.setState({
25 | a: val,
26 | old
27 | })
28 | }
29 | }
30 |
31 | render(){
32 | return this.state.a;
33 | }
34 | }
35 |
36 | let C = watchProps(App);
37 | let wrapper = mount();
38 | t.is(wrapper.text(), 'a');
39 | setTimeout(()=>{
40 | wrapper.setProps({
41 | x: 'b'
42 | })
43 | },50)
44 | setTimeout(()=>{
45 | t.is(wrapper.text(), 'b');
46 | let state = wrapper.children().state();
47 | t.is(state.old, 'a')
48 | t.is(num, 1)
49 | },100)
50 |
51 | setTimeout(()=>{
52 | wrapper.setProps({
53 | x: 'c'
54 | })
55 | },150)
56 |
57 | setTimeout(()=>{
58 | wrapper.setProps({
59 | x: 'd'
60 | })
61 | },160)
62 |
63 | setTimeout(()=>{
64 | t.is(wrapper.text(), 'd');
65 | let state = wrapper.children().state();
66 | t.is(state.old, 'c')
67 | t.is(num, 3)
68 | t.end();
69 | },200)
70 |
71 | })
72 |
73 |
74 | test.cb('React 15', t=>{
75 | let num = 0;
76 | React.version = '15.1.0'
77 | class App extends React.Component{
78 | static propTypes = {
79 | x: PropTypes.string
80 | }
81 | state={
82 | a: this.props.x
83 | }
84 |
85 | watch ={
86 | x: function(val, old){
87 | num++;
88 | this.setState({
89 | a: val,
90 | old
91 | })
92 | }
93 | }
94 |
95 | render(){
96 | return this.state.a;
97 | }
98 | }
99 |
100 | let C = watchProps(App);
101 | let wrapper = mount();
102 | t.is(wrapper.text(), 'a');
103 | setTimeout(()=>{
104 | wrapper.setProps({
105 | x: 'b'
106 | })
107 | },50)
108 | setTimeout(()=>{
109 | t.is(wrapper.text(), 'b');
110 | let state = wrapper.children().state();
111 | t.is(state.old, 'a')
112 | t.is(num, 1)
113 | },100)
114 |
115 | setTimeout(()=>{
116 | wrapper.setProps({
117 | x: 'c'
118 | })
119 | },150)
120 |
121 | setTimeout(()=>{
122 | wrapper.setProps({
123 | x: 'd'
124 | })
125 | },160)
126 |
127 | setTimeout(()=>{
128 | t.is(wrapper.text(), 'd');
129 | let state = wrapper.children().state();
130 | t.is(state.old, 'c')
131 | t.is(num, 3)
132 | t.end();
133 | },200)
134 |
135 | })
136 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require ('path');
2 |
3 | module.exports = {
4 | mode: 'production',
5 | module: {
6 | rules: [
7 | {
8 | test: /\.js$/,
9 | exclude: /node_modules/,
10 | use: {
11 | loader: 'babel-loader',
12 | },
13 | }
14 | ],
15 | },
16 | entry: './src/index.js',
17 | output: {
18 | filename: 'watch-props.js',
19 | path: path.resolve (__dirname, 'dist'),
20 | libraryTarget: "umd",
21 | library: ['watchProps'],
22 | },
23 | externals: [
24 | { react: { commonjs: "react", commonjs2: "react",amd: 'react', root: ['React'] } },
25 | ],
26 | optimization: {
27 | minimize: false
28 | }
29 | };
30 |
--------------------------------------------------------------------------------