diff --git a/.babelrc b/.babelrc
index 0b2bb39..23744b7 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,3 +1,3 @@
{
- "presets": ["react", "es2015"],
+ "presets": ["react", "env"]
}
diff --git a/.eslintrc b/.eslintrc
index 67b82c5..e7bb059 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,42 +1,298 @@
{
+ "extends": [
+ "eslint:recommended",
+ "prettier"
+ ],
+ "parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
+ "arrowFunctions": true,
+ "blockBindings": true,
+ "classes": true,
+ "defaultParams": true,
+ "destructuring": true,
+ "forOf": true,
+ "generators": true,
+ "modules": true,
+ "templateStrings": true,
"jsx": true
}
},
- "rules": {
- "semi": 2,
- "no-unused-vars": 2,
- "react/display-name": 2,
- "react/jsx-key": 2,
- "react/jsx-no-comment-textnodes": 2,
- "react/jsx-no-duplicate-props": 2,
- "react/jsx-no-target-blank": 2,
- "react/jsx-no-undef": 2,
- "react/jsx-uses-react": 2,
- "react/jsx-uses-vars": 2,
- "react/no-children-prop": 2,
- "react/no-danger-with-children": 2,
- "react/no-deprecated": 2,
- "react/no-direct-mutation-state": 2,
- "react/no-find-dom-node": 2,
- "react/no-is-mounted": 2,
- "react/no-render-return-value": 2,
- "react/no-string-refs": 2,
- "react/no-unescaped-entities": 2,
- "react/no-unknown-property": 2,
- "react/prop-types": 2,
- "react/react-in-jsx-scope": 2,
- "react/require-render-return": 2,
+ "env": {
+ "browser": true,
+ "es6": true,
+ "jasmine": true,
+ "jest": true,
+ "node": true
+ },
+ "globals": {
+ "jest": true
},
"plugins": [
- "react"
+ "react",
+ "import"
],
- "settings": {
- "react": {
- "version": "^15.6.1"
+ "overrides": [
+ {
+ "files": [
+ "**/*.percy.{js,jsx}"
+ ],
+ "env": {
+ "react-percy/globals": true
+ }
}
+ ],
+ "rules": {
+ "accessor-pairs": [
+ "error"
+ ],
+ "block-scoped-var": [
+ "error"
+ ],
+ "consistent-return": [
+ "error"
+ ],
+ "curly": [
+ "error",
+ "all"
+ ],
+ "default-case": [
+ "error"
+ ],
+ "dot-location": [
+ "off"
+ ],
+ "dot-notation": [
+ "error"
+ ],
+ "eqeqeq": [
+ "error"
+ ],
+ "guard-for-in": [
+ "off"
+ ],
+ "import/named": [
+ "off"
+ ],
+ "import/no-duplicates": [
+ "error"
+ ],
+ "import/no-named-as-default": [
+ "error"
+ ],
+ "new-cap": [
+ "error"
+ ],
+ "no-alert": [
+ 1
+ ],
+ "no-caller": [
+ "error"
+ ],
+ "no-case-declarations": [
+ "error"
+ ],
+ "no-console": [
+ "error"
+ ],
+ "no-div-regex": [
+ "error"
+ ],
+ "no-dupe-keys": [
+ "error"
+ ],
+ "no-else-return": [
+ "error"
+ ],
+ "no-empty-pattern": [
+ "error"
+ ],
+ "no-eq-null": [
+ "error"
+ ],
+ "no-eval": [
+ "error"
+ ],
+ "no-extend-native": [
+ "error"
+ ],
+ "no-extra-bind": [
+ "error"
+ ],
+ "no-extra-boolean-cast": [
+ "error"
+ ],
+ "no-inline-comments": [
+ "error"
+ ],
+ "no-implicit-coercion": [
+ "error"
+ ],
+ "no-implied-eval": [
+ "error"
+ ],
+ "no-inner-declarations": [
+ "off"
+ ],
+ "no-invalid-this": [
+ "error"
+ ],
+ "no-iterator": [
+ "error"
+ ],
+ "no-labels": [
+ "error"
+ ],
+ "no-lone-blocks": [
+ "error"
+ ],
+ "no-loop-func": [
+ "error"
+ ],
+ "no-multi-str": [
+ "error"
+ ],
+ "no-native-reassign": [
+ "error"
+ ],
+ "no-new": [
+ "error"
+ ],
+ "no-new-func": [
+ "error"
+ ],
+ "no-new-wrappers": [
+ "error"
+ ],
+ "no-param-reassign": [
+ "error"
+ ],
+ "no-process-env": [
+ "warn"
+ ],
+ "no-proto": [
+ "error"
+ ],
+ "no-redeclare": [
+ "error"
+ ],
+ "no-return-assign": [
+ "error"
+ ],
+ "no-script-url": [
+ "error"
+ ],
+ "no-self-compare": [
+ "error"
+ ],
+ "no-sequences": [
+ "error"
+ ],
+ "no-shadow": [
+ "off"
+ ],
+ "no-throw-literal": [
+ "error"
+ ],
+ "no-undefined": [
+ "error"
+ ],
+ "no-unused-expressions": [
+ "error"
+ ],
+ "no-use-before-define": [
+ "error",
+ "nofunc"
+ ],
+ "no-useless-call": [
+ "error"
+ ],
+ "no-useless-concat": [
+ "error"
+ ],
+ "no-with": [
+ "error"
+ ],
+ "prefer-const": [
+ "error"
+ ],
+ "radix": [
+ "error"
+ ],
+ "react/jsx-no-duplicate-props": [
+ "error"
+ ],
+ "react/jsx-no-undef": [
+ "error"
+ ],
+ "react/jsx-uses-react": [
+ "error"
+ ],
+ "react/jsx-uses-vars": [
+ "error"
+ ],
+ "react/no-did-update-set-state": [
+ "error"
+ ],
+ "react/no-direct-mutation-state": [
+ "error"
+ ],
+ "react/no-is-mounted": [
+ "error"
+ ],
+ "react/no-unknown-property": [
+ "error"
+ ],
+ "react/prefer-es6-class": [
+ "error",
+ "always"
+ ],
+ "react/prop-types": "error",
+ "valid-jsdoc": [
+ "error"
+ ],
+ "yoda": [
+ "error"
+ ],
+ "spaced-comment": [
+ "error",
+ "always",
+ {
+ "block": {
+ "exceptions": [
+ "*"
+ ]
+ }
+ }
+ ],
+ "no-unused-vars": [
+ "error",
+ {
+ "args": "after-used",
+ "argsIgnorePattern": "^_",
+ "caughtErrorsIgnorePattern": "^e$"
+ }
+ ],
+ "no-magic-numbers": [
+ "error",
+ {
+ "ignoreArrayIndexes": true,
+ "ignore": [
+ -1,
+ 0,
+ 1,
+ 2,
+ 3,
+ 100,
+ 10,
+ 0.5
+ ]
+ }
+ ],
+ "no-underscore-dangle": [
+ "off"
+ ]
}
}
diff --git a/.prettierrc b/.prettierrc
index 9361dea..73d0133 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,5 +1,6 @@
{
- "singleQuote": true,
- "bracketSpacing": false,
- "trailingComma": "es5"
+ "singleQuote": true,
+ "bracketSpacing": false,
+ "trailingComma": "es5",
+ "printWidth": 100
}
diff --git a/README.md b/README.md
index 4984770..23f2836 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# react-plotly.js
-
+
> A [plotly.js](https://github.com/plotly/plotly.js) React component from
> [Plotly](https://plot.ly/). The basis of Plotly's
diff --git a/package.json b/package.json
index 90ff178..7fd82ff 100644
--- a/package.json
+++ b/package.json
@@ -13,8 +13,8 @@
"url": "https://github.com/plotly/react-plotly.js/issues"
},
"scripts": {
- "make:lib": "mkdirp lib && babel src --out-dir=lib --ignore __tests__/*.js,__mocks__/*.js --presets=es2015,react --source-maps --plugins babel-plugin-add-module-exports && mv lib/* ./ && rmdir lib",
- "make:dist": "mkdirp dist && browserify src/factory.js -o ./dist/create-plotly-component.js -t [ babelify --presets [ es2015 react ] --plugins add-module-exports ] -t browserify-global-shim --standalone createPlotlyComponent && uglifyjs ./dist/create-plotly-component.js --compress --mangle --output ./dist/create-plotly-component.min.js --source-map filename=dist/create-plotly-component.min.js.map",
+ "make:lib": "mkdirp lib && babel src --out-dir=lib --ignore __tests__/*.js,__mocks__/*.js --presets=env,react --source-maps --plugins babel-plugin-add-module-exports && mv lib/* ./ && rmdir lib",
+ "make:dist": "mkdirp dist && browserify src/factory.js -o ./dist/create-plotly-component.js -t [ babelify --presets [ env react ] --plugins add-module-exports ] -t browserify-global-shim --standalone createPlotlyComponent && uglifyjs ./dist/create-plotly-component.js --compress --mangle --output ./dist/create-plotly-component.min.js --source-map filename=dist/create-plotly-component.min.js.map",
"clean": "rimraf lib dist react-plotly.js react-plotly.js.map factory.js factory.js.map",
"prepublishOnly": "npm run clean && npm run make:lib && npm run make:dist",
"lint": "prettier --trailing-comma es5 --write \"src/**/*.js\" && eslint src",
@@ -33,9 +33,10 @@
],
"devDependencies": {
"babel-cli": "^6.24.1",
+ "babel-eslint": "^10.0.1",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-class-properties": "^6.24.1",
- "babel-preset-es2015": "^6.24.1",
+ "babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babelify": "^7.3.0",
"brfs": "^1.4.3",
@@ -45,6 +46,8 @@
"dependency-check": "^2.9.1",
"enzyme": "^2.9.1",
"eslint": "^4.8.0",
+ "eslint-config-prettier": "^4.0.0",
+ "eslint-plugin-import": "^2.16.0",
"eslint-plugin-react": "^7.4.0",
"event-emitter": "^0.3.5",
"jest": "^20.0.4",
@@ -63,7 +66,7 @@
},
"peerDependencies": {
"plotly.js": ">1.34.0",
- "react": ">12.0.0"
+ "react": ">0.13.0"
},
"browserify-global-shim": {
"react": "React"
diff --git a/src/__mocks__/plotly.js b/src/__mocks__/plotly.js
index 8cc7d89..b6f1f38 100644
--- a/src/__mocks__/plotly.js
+++ b/src/__mocks__/plotly.js
@@ -12,7 +12,7 @@ export default {
}),
newPlot: jest.fn(gd => {
state.gd = gd;
- EventEmitter(state.gd);
+ EventEmitter(state.gd); // eslint-disable-line new-cap
setTimeout(() => {
state.gd.emit('plotly_afterplot');
@@ -20,7 +20,7 @@ export default {
}),
react: jest.fn(gd => {
state.gd = gd;
- EventEmitter(state.gd);
+ EventEmitter(state.gd); // eslint-disable-line new-cap
setTimeout(() => {
state.gd.emit('plotly_afterplot');
@@ -40,6 +40,6 @@ export default {
}),
update: jest.fn(),
purge: jest.fn(() => {
- state.gd = nll;
+ state.gd = null;
}),
};
diff --git a/src/__tests__/react-plotly.test.js b/src/__tests__/react-plotly.test.js
index 4db358c..e6d6e6c 100644
--- a/src/__tests__/react-plotly.test.js
+++ b/src/__tests__/react-plotly.test.js
@@ -9,11 +9,7 @@ describe('', () => {
function createPlot(props) {
return new Promise((resolve, reject) => {
const plot = mount(
- resolve(plot)}
- onError={reject}
- />
+ resolve(plot)} onError={reject} />
);
});
}
@@ -24,9 +20,9 @@ describe('', () => {
Object.assign(
defaultArgs || {
data: [],
- config: undefined,
- layout: undefined,
- frames: undefined,
+ config: undefined, // eslint-disable-line no-undefined
+ layout: undefined, // eslint-disable-line no-undefined
+ frames: undefined, // eslint-disable-line no-undefined
},
props || {}
)
@@ -139,19 +135,11 @@ describe('', () => {
})
.then(plot => {
// Update with and without revision bumps:
+ /* eslint-disable no-magic-numbers */
setTimeout(() => plot.setProps({layout: {title: 'test test'}}), 10);
- setTimeout(
- () => plot.setProps({revision: 1, layout: {title: 'test test'}}),
- 20
- );
- setTimeout(
- () => plot.setProps({revision: 1, layout: {title: 'test test'}}),
- 30
- );
- setTimeout(
- () => plot.setProps({revision: 2, layout: {title: 'test test'}}),
- 40
- );
+ setTimeout(() => plot.setProps({revision: 1, layout: {title: 'test test'}}), 20);
+ setTimeout(() => plot.setProps({revision: 1, layout: {title: 'test test'}}), 30);
+ setTimeout(() => plot.setProps({revision: 2, layout: {title: 'test test'}}), 40);
})
.catch(err => done.fail(err));
});
diff --git a/src/factory.js b/src/factory.js
index c207fcb..23a0cb3 100644
--- a/src/factory.js
+++ b/src/factory.js
@@ -64,8 +64,20 @@ export default function plotComponentFactory(Plotly) {
}
componentDidMount() {
+ this.unmounting = false;
+
this.p = this.p
.then(() => {
+ if (!this.el) {
+ let error;
+ if (this.unmounting) {
+ error = new Error('Component is unmounting');
+ error.reason = 'unmounting';
+ } else {
+ error = new Error('Missing element reference');
+ }
+ throw error;
+ }
return Plotly.newPlot(this.el, {
data: this.props.data,
layout: this.props.layout,
@@ -78,28 +90,28 @@ export default function plotComponentFactory(Plotly) {
.then(this.attachUpdateEvents)
.then(() => this.figureCallback(this.props.onInitialized))
.catch(err => {
- console.error('Error while plotting:', err);
- return this.props.onError && this.props.onError(err);
+ if (err.reason === 'unmounting') {
+ return;
+ }
+ console.error('Error while plotting:', err); // eslint-disable-line no-console
+ if (this.props.onError) {
+ this.props.onError(err);
+ }
});
}
componentWillUpdate(nextProps) {
- if (
- nextProps.revision !== void 0 &&
- nextProps.revision === this.props.revision
- ) {
+ this.unmounting = false;
+
+ if (nextProps.revision !== void 0 && nextProps.revision === this.props.revision) {
// if revision is set and unchanged, do nothing
return;
}
const numPrevFrames =
- this.props.frames && this.props.frames.length
- ? this.props.frames.length
- : 0;
+ this.props.frames && this.props.frames.length ? this.props.frames.length : 0;
const numNextFrames =
- nextProps.frames && nextProps.frames.length
- ? nextProps.frames.length
- : 0;
+ nextProps.frames && nextProps.frames.length ? nextProps.frames.length : 0;
if (
nextProps.layout === this.props.layout &&
nextProps.data === this.props.data &&
@@ -113,6 +125,16 @@ export default function plotComponentFactory(Plotly) {
this.p = this.p
.then(() => {
+ if (!this.el) {
+ let error;
+ if (this.unmounting) {
+ error = new Error('Component is unmounting');
+ error.reason = 'unmounting';
+ } else {
+ error = new Error('Missing element reference');
+ }
+ throw error;
+ }
return Plotly.react(this.el, {
data: nextProps.data,
layout: nextProps.layout,
@@ -124,12 +146,19 @@ export default function plotComponentFactory(Plotly) {
.then(() => this.syncWindowResize(nextProps))
.then(() => this.figureCallback(nextProps.onUpdate))
.catch(err => {
- console.error('Error while plotting:', err);
- this.props.onError && this.props.onError(err);
+ if (err.reason === 'unmounting') {
+ return;
+ }
+ console.error('Error while plotting:', err); // eslint-disable-line no-console
+ if (this.props.onError) {
+ this.props.onError(err);
+ }
});
}
componentWillUnmount() {
+ this.unmounting = true;
+
this.figureCallback(this.props.onPurge);
if (this.resizeHandler && isBrowser) {
@@ -143,7 +172,9 @@ export default function plotComponentFactory(Plotly) {
}
attachUpdateEvents() {
- if (!this.el || !this.el.removeListener) return;
+ if (!this.el || !this.el.removeListener) {
+ return;
+ }
for (let i = 0; i < updateEvents.length; i++) {
this.el.on(updateEvents[i], this.handleUpdate);
@@ -151,7 +182,9 @@ export default function plotComponentFactory(Plotly) {
}
removeUpdateEvents() {
- if (!this.el || !this.el.removeListener) return;
+ if (!this.el || !this.el.removeListener) {
+ return;
+ }
for (let i = 0; i < updateEvents.length; i++) {
this.el.removeListener(updateEvents[i], this.handleUpdate);
@@ -165,17 +198,17 @@ export default function plotComponentFactory(Plotly) {
figureCallback(callback) {
if (typeof callback === 'function') {
const {data, layout} = this.el;
- const frames = this.el._transitionData
- ? this.el._transitionData._frames
- : null;
- const figure = {data, layout, frames}; // for extra clarity!
+ const frames = this.el._transitionData ? this.el._transitionData._frames : null;
+ const figure = {data, layout, frames};
callback(figure, this.el);
}
}
syncWindowResize(propsIn, invoke) {
const props = propsIn || this.props;
- if (!isBrowser) return;
+ if (!isBrowser) {
+ return;
+ }
if (props.useResizeHandler && !this.resizeHandler) {
this.resizeHandler = () => {
@@ -207,20 +240,14 @@ export default function plotComponentFactory(Plotly) {
for (let i = 0; i < eventNames.length; i++) {
const eventName = eventNames[i];
const prop = props['on' + eventName];
- const hasHandler = !!this.handlers[eventName];
+ const hasHandler = Boolean(this.handlers[eventName]);
if (prop && !hasHandler) {
this.handlers[eventName] = prop;
- this.el.on(
- 'plotly_' + eventName.toLowerCase(),
- this.handlers[eventName]
- );
+ this.el.on('plotly_' + eventName.toLowerCase(), this.handlers[eventName]);
} else if (!prop && hasHandler) {
// Needs to be removed:
- this.el.removeListener(
- 'plotly_' + eventName.toLowerCase(),
- this.handlers[eventName]
- );
+ this.el.removeListener('plotly_' + eventName.toLowerCase(), this.handlers[eventName]);
delete this.handlers[eventName];
}
}