diff --git a/commonjs/withJob.js b/commonjs/withJob.js index 990bc5f..6d4e27c 100644 --- a/commonjs/withJob.js +++ b/commonjs/withJob.js @@ -105,7 +105,7 @@ function withJob(config) { this.setState({ data: result ? result.data : null, - error: null, + error: result ? result.error : null, completed: result != null }); } @@ -140,7 +140,6 @@ function withJob(config) { error = _state.error, completed = _state.completed; - if (error) { return ErrorComponent ? _react2.default.createElement(ErrorComponent, _extends({}, this.props, { error: error })) : null; } @@ -210,7 +209,17 @@ function withJob(config) { // eslint-disable-next-line no-console console.warn('Failed to resolve job'); // eslint-disable-next-line no-console - console.warn(error); + console.warn(error.message); + // eslint-disable-next-line no-console + console.warn(error.stack); + if (_this2.context.jobs) { + _this2.context.jobs.register(id, { + error: { + message: error.message, + stack: error.stack + } + }); + } } // Ensures asyncBootstrap stops return false; diff --git a/package-lock.json b/package-lock.json index 0d74467..704be4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-jobs", - "version": "1.0.0-beta.3", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 363de6d..e0e1325 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,16 @@ { "name": "react-jobs", "version": "1.0.0", - "description": - "Attach asynchronous/synchronous \"jobs\" to your components, with SSR support.", + "description": "Attach asynchronous/synchronous \"jobs\" to your components, with SSR support.", "license": "MIT", "main": "commonjs/index.js", - "files": ["*.js", "*.md", "umd", "commonjs", "ssr.js"], + "files": [ + "*.js", + "*.md", + "umd", + "commonjs", + "ssr.js" + ], "repository": { "type": "git", "url": "https://github.com/ctrlplusb/react-jobs.git" @@ -24,8 +29,7 @@ ], "scripts": { "build": "babel-node ./tools/scripts/build.js", - "clean": - "rimraf ./commonjs && rimraf ./umd && rimraf ./coverage && rimraf ./flow-coverage && rimraf ./umd", + "clean": "rimraf ./commonjs && rimraf ./umd && rimraf ./coverage && rimraf ./flow-coverage && rimraf ./umd", "lint": "eslint src", "precommit": "lint-staged", "prepush": "jest", @@ -36,7 +40,7 @@ }, "peerDependencies": { "prop-types": "^15.0.0", - "react": "^14.0.0 || ^15.0.0" + "react": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "devDependencies": { "app-root-dir": "1.0.2", @@ -83,14 +87,21 @@ "webpack-hot-middleware": "^2.19.1" }, "jest": { - "collectCoverageFrom": ["src/**/*.{js,jsx}"], - "snapshotSerializers": ["/node_modules/enzyme-to-json/serializer"], + "collectCoverageFrom": [ + "src/**/*.{js,jsx}" + ], + "snapshotSerializers": [ + "/node_modules/enzyme-to-json/serializer" + ], "testPathIgnorePatterns": [ "/(commonjs|coverage|flow-typed|node_modules|tools|umd)/" ] }, "lint-staged": { - "src/**/*.js": ["prettier --write", "git add"] + "src/**/*.js": [ + "prettier --write", + "git add" + ] }, "eslintConfig": { "root": true, @@ -103,7 +114,10 @@ "extends": "airbnb", "rules": { "array-callback-return": 0, - "arrow-parens": ["error", "as-needed"], + "arrow-parens": [ + "error", + "as-needed" + ], "camelcase": 0, "import/prefer-default-export": 0, "import/no-extraneous-dependencies": 0, @@ -114,7 +128,10 @@ "no-nested-ternary": 0, "react/no-array-index-key": 0, "react/react-in-jsx-scope": 0, - "semi": [2, "never"], + "semi": [ + 2, + "never" + ], "react/forbid-prop-types": 0, "react/jsx-filename-extension": 0, "react/sort-comp": 0 diff --git a/src/__tests__/__snapshots__/integration.test.js.snap b/src/__tests__/__snapshots__/integration.test.js.snap index 1219980..4523d9c 100644 --- a/src/__tests__/__snapshots__/integration.test.js.snap +++ b/src/__tests__/__snapshots__/integration.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`integration tests render server and client 1`] = `"
Hello
World
Goodbye
"`; +exports[`integration tests render server and client 1`] = `"
Hello
World
Goodbye
Oh noes!
"`; exports[`integration tests render server and client 2`] = ` Object { @@ -14,6 +14,12 @@ Object { "3": Object { "data": "Goodbye", }, + "4": Object { + "error": Object { + "message": "Oh noes!", + "stack": "fake stack", + }, + }, }, } `; @@ -57,7 +63,20 @@ exports[`integration tests render server and client 3`] = ` - + + +
+ Oh noes! +
+
+
`; @@ -103,7 +122,12 @@ exports[`integration tests render server and client 4`] = `
Oh noes! diff --git a/src/__tests__/integration.test.js b/src/__tests__/integration.test.js index 5513810..bbe8e5d 100644 --- a/src/__tests__/integration.test.js +++ b/src/__tests__/integration.test.js @@ -18,6 +18,8 @@ function ResultRenderer({ jobResult, children }) { ) } const ErrorComponent = ({ error }) =>
{error ? error.message : null}
+const error = new Error('Oh noes!') +error.stack = 'fake stack' const createComponents = () => ({ Hello: withJob({ work: () => resolveAfter(workTime, 'Hello') })( @@ -30,7 +32,7 @@ const createComponents = () => ({ ResultRenderer, ), Fail: withJob({ - work: () => rejectAfter(workTime, new Error('Oh noes!')), + work: () => rejectAfter(workTime, error), ErrorComponent, })(ResultRenderer), }) diff --git a/src/withJob.js b/src/withJob.js index 5a12cae..5067e8b 100644 --- a/src/withJob.js +++ b/src/withJob.js @@ -32,7 +32,7 @@ export default function withJob(config) { let id class ComponentWithJob extends Component { - static displayName = `WithJob(${getDisplayName(WrappedComponent)})`; + static displayName = `WithJob(${getDisplayName(WrappedComponent)})` static contextTypes = { jobs: PropTypes.shape({ @@ -42,7 +42,7 @@ export default function withJob(config) { getRehydrate: PropTypes.func.isRequired, removeRehydrate: PropTypes.func.isRequired, }), - }; + } constructor(props, context) { super(props, context) @@ -69,14 +69,15 @@ export default function withJob(config) { let result if (this.context.jobs) { - result = env === 'browser' - ? this.context.jobs.getRehydrate(id) - : this.context.jobs.get(id) + result = + env === 'browser' + ? this.context.jobs.getRehydrate(id) + : this.context.jobs.get(id) } this.setState({ data: result ? result.data : null, - error: null, + error: result ? result.error : null, completed: result != null, }) } @@ -107,7 +108,7 @@ export default function withJob(config) { } } - resolveWork = (props) => { + resolveWork = props => { let workDefinition this.setState({ completed: false, data: null, error: null }) @@ -123,7 +124,7 @@ export default function withJob(config) { if (isPromise(workDefinition)) { // Asynchronous result. return workDefinition - .then((data) => { + .then(data => { if (this.unmounted) { return undefined } @@ -134,19 +135,16 @@ export default function withJob(config) { // Ensures asyncBootstrap continues return true }) - .catch((error) => { + .catch(error => { if (this.unmounted) { return undefined } if (env === 'browser') { - setTimeout( - () => { - if (!this.unmounted) { - this.setState({ completed: true, error }) - } - }, - 16, - ) + setTimeout(() => { + if (!this.unmounted) { + this.setState({ completed: true, error }) + } + }, 16) } else { // node // We will at least log the error so that user isn't completely @@ -154,7 +152,17 @@ export default function withJob(config) { // eslint-disable-next-line no-console console.warn('Failed to resolve job') // eslint-disable-next-line no-console - console.warn(error) + console.warn(error.message) + // eslint-disable-next-line no-console + console.warn(error.stack) + if (this.context.jobs) { + this.context.jobs.register(id, { + error: { + message: error.message, + stack: error.stack, + }, + }) + } } // Ensures asyncBootstrap stops return false @@ -166,21 +174,20 @@ export default function withJob(config) { // Ensures asyncBootstrap continues return true - }; + } getJobState = () => ({ completed: this.state.completed, error: this.state.error, data: this.state.data, - }); + }) render() { const { data, error, completed } = this.state - if (error) { - return ErrorComponent - ? - : null + return ErrorComponent ? ( + + ) : null } if (!completed) { return LoadingComponent ? : null diff --git a/umd/react-jobs.js b/umd/react-jobs.js index 5469272..8049dd7 100644 --- a/umd/react-jobs.js +++ b/umd/react-jobs.js @@ -261,7 +261,7 @@ function withJob(config) { this.setState({ data: result ? result.data : null, - error: null, + error: result ? result.error : null, completed: result != null }); } @@ -296,7 +296,6 @@ function withJob(config) { error = _state.error, completed = _state.completed; - if (error) { return ErrorComponent ? _react2.default.createElement(ErrorComponent, _extends({}, this.props, { error: error })) : null; } @@ -366,7 +365,17 @@ function withJob(config) { // eslint-disable-next-line no-console console.warn('Failed to resolve job'); // eslint-disable-next-line no-console - console.warn(error); + console.warn(error.message); + // eslint-disable-next-line no-console + console.warn(error.stack); + if (_this2.context.jobs) { + _this2.context.jobs.register(id, { + error: { + message: error.message, + stack: error.stack + } + }); + } } // Ensures asyncBootstrap stops return false; diff --git a/umd/react-jobs.min.js b/umd/react-jobs.min.js index b13c81d..d3dd89c 100644 --- a/umd/react-jobs.min.js +++ b/umd/react-jobs.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("prop-types")):"function"==typeof define&&define.amd?define(["react","prop-types"],t):"object"==typeof exports?exports.ReactJobs=t(require("react"),require("prop-types")):e.ReactJobs=t(e.React,e.PropTypes)}(this,function(e,t){return function(e){function t(r){if(o[r])return o[r].exports;var n=o[r]={i:r,l:!1,exports:{}};return e[r].call(n.exports,n,n.exports,t),n.l=!0,n.exports}var o={};return t.m=e,t.c=o,t.d=function(e,o,r){t.o(e,o)||Object.defineProperty(e,o,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var o=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(o,"a",o),o},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=3)}([function(t,o){t.exports=e},function(e,o){e.exports=t},function(e,t,o){"use strict";function r(){var e=0,t={};return{getNextId:function(){return e+=1},resetIds:function(){e=0},register:function(e,o){t[e]=o},get:function(e){return t[e]},getState:function(){return{jobs:t}}}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r},function(e,t,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.createJobContext=t.JobProvider=t.withJob=void 0;var n=o(4),u=r(n),i=o(6),a=r(i),s=o(2),c=r(s);t.withJob=u.default,t.JobProvider=a.default,t.createJobContext=c.default},function(e,t,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function a(e){if("object"!==(void 0===e?"undefined":f(e)))throw new Error("You must provide a config object to withJob");var t=e.work,o=e.LoadingComponent,r=e.ErrorComponent,a=e.serverMode,p=void 0===a?"resolve":a,m=e.shouldWorkAgain,j=void 0===m?v:m;if("function"!=typeof t)throw new Error("You must provide a work function to withJob");if(-1===h.indexOf(p))throw new Error("Invalid serverMode provided to asyncComponent");var g="undefined"==typeof window?"node":"browser";return function(e){var a=void 0,f=function(t){function f(e,t){n(this,f);var o=u(this,(f.__proto__||Object.getPrototypeOf(f)).call(this,e,t));return h.call(o),t.jobs&&(a=t.jobs.getNextId()),o}return i(f,t),c(f,[{key:"asyncBootstrap",value:function(){return"browser"===g||"defer"!==p&&this.resolveWork(this.props)}},{key:"componentWillMount",value:function(){var e=void 0;this.context.jobs&&(e="browser"===g?this.context.jobs.getRehydrate(a):this.context.jobs.get(a)),this.setState({data:e?e.data:null,error:null,completed:null!=e})}},{key:"componentDidMount",value:function(){this.state.completed||this.resolveWork(this.props),this.context.jobs&&"browser"===g&&this.context.jobs.removeRehydrate(a)}},{key:"componentWillUnmount",value:function(){this.unmounted=!0}},{key:"componentWillReceiveProps",value:function(e){j((0,y.propsWithoutInternal)(this.props),(0,y.propsWithoutInternal)(e),this.getJobState())&&this.resolveWork(e)}},{key:"render",value:function(){var t=this.state,n=t.data,u=t.error,i=t.completed;return u?r?d.default.createElement(r,s({},this.props,{error:u})):null:i?d.default.createElement(e,s({},this.props,{jobResult:n})):o?d.default.createElement(o,this.props):null}}]),f}(l.Component);f.displayName="WithJob("+(0,y.getDisplayName)(e)+")",f.contextTypes={jobs:b.default.shape({getNextId:b.default.func.isRequired,register:b.default.func.isRequired,get:b.default.func.isRequired,getRehydrate:b.default.func.isRequired,removeRehydrate:b.default.func.isRequired})};var h=function(){var e=this;this.resolveWork=function(o){var r=void 0;e.setState({completed:!1,data:null,error:null});try{r=t(o)}catch(t){return e.setState({completed:!0,error:t}),!1}return(0,y.isPromise)(r)?r.then(function(t){if(!e.unmounted)return e.setState({completed:!0,data:t}),e.context.jobs&&e.context.jobs.register(a,{data:t}),!0}).catch(function(t){if(!e.unmounted)return"browser"===g?setTimeout(function(){e.unmounted||e.setState({completed:!0,error:t})},16):(console.warn("Failed to resolve job"),console.warn(t)),!1}):(e.setState({completed:!0,data:r,error:null}),!0)},this.getJobState=function(){return{completed:e.state.completed,error:e.state.error,data:e.state.data}}};return f}}Object.defineProperty(t,"__esModule",{value:!0});var s=Object.assign||function(e){for(var t=1;t=0||Object.prototype.hasOwnProperty.call(e,r)&&(o[r]=e[r]);return o}function n(e){return e.displayName||e.name||"Component"}Object.defineProperty(t,"__esModule",{value:!0});var u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.getDisplayName=n;var i=(t.isPromise=function(e){return"object"===(void 0===e?"undefined":u(e))&&"function"==typeof e.then},function(e){e.jobInitState,e.onJobProcessed;return r(e,["jobInitState","onJobProcessed"])});t.propsWithoutInternal=i},function(e,t,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var o=0;o=0||Object.prototype.hasOwnProperty.call(e,r)&&(o[r]=e[r]);return o}function n(e){return e.displayName||e.name||"Component"}Object.defineProperty(t,"__esModule",{value:!0});var u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.getDisplayName=n;var i=(t.isPromise=function(e){return"object"===(void 0===e?"undefined":u(e))&&"function"==typeof e.then},function(e){e.jobInitState,e.onJobProcessed;return r(e,["jobInitState","onJobProcessed"])});t.propsWithoutInternal=i},function(e,t,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var o=0;o