Skip to content

Remove deprecated react methods; refactor #128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions src/__tests__/react-plotly.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ describe('<Plotly/>', () => {
});

describe('initialization', function() {
test('calls Plotly.newPlot on instantiation', done => {
test('calls Plotly.react on instantiation', done => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you know, I forgot we have minimal tests for this thing... Could you please add some tests for the logic in #126 please? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(you can add the tests in a separate PR, but we should get them in)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

createPlot({})
.then(() => {
expect(Plotly.newPlot).toHaveBeenCalled();
expect(Plotly.react).toHaveBeenCalled();
})
.catch(err => {
done.fail(err);
Expand All @@ -61,7 +61,7 @@ describe('<Plotly/>', () => {
layout: {title: 'foo'},
})
.then(() => {
expectPlotlyAPICall(Plotly.newPlot, {
expectPlotlyAPICall(Plotly.react, {
data: [{x: [1, 2, 3]}],
layout: {title: 'foo'},
});
Expand All @@ -75,7 +75,7 @@ describe('<Plotly/>', () => {
layout: {width: 320, height: 240},
})
.then(() => {
expectPlotlyAPICall(Plotly.newPlot, {
expectPlotlyAPICall(Plotly.react, {
layout: {width: 320, height: 240},
});
})
Expand All @@ -102,6 +102,24 @@ describe('<Plotly/>', () => {
.catch(err => done.fail(err));
});

test('updates data when revision is defined but not changed', done => {
createPlot({
revision: 1,
layout: {width: 123, height: 456},
onUpdate: once(() => {
expectPlotlyAPICall(Plotly.react, {
data: [{x: [1, 2, 3]}],
layout: {width: 123, height: 456},
});
done();
}),
})
.then(plot => {
plot.setProps({revision: 1, data: [{x: [1, 2, 3]}]});
})
.catch(err => done.fail(err));
});

test('sets the title', done => {
createPlot({
onUpdate: once(() => {
Expand Down Expand Up @@ -129,7 +147,7 @@ describe('<Plotly/>', () => {
// we've checked the third and fourth calls:
if (callCnt === 2) {
setTimeout(() => {
expect(Plotly.newPlot).not.toHaveBeenCalledTimes(2);
expect(Plotly.react).not.toHaveBeenCalledTimes(2);
done();
}, 100);
}
Expand Down
108 changes: 38 additions & 70 deletions src/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ export default function plotComponentFactory(Plotly) {
this.getRef = this.getRef.bind(this);
this.handleUpdate = this.handleUpdate.bind(this);
this.figureCallback = this.figureCallback.bind(this);
this.updatePlotly = this.updatePlotly.bind(this);
}

componentDidMount() {
this.unmounting = false;

updatePlotly(shouldInvokeResizeHandler, figureCallbackFunction, shouldAttachUpdateEvents) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for whatever reason, aesthetically it seems nicer to me to have the should* both at the end, but I can live with it as is of course.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the lifecycle methods? Yeah, I was on the fence here. I decided to do updatePlotly first, because it's called by those two...

this.p = this.p
.then(() => {
if (!this.el) {
Expand All @@ -78,17 +77,17 @@ export default function plotComponentFactory(Plotly) {
}
throw error;
}
return Plotly.newPlot(this.el, {
return Plotly.react(this.el, {
data: this.props.data,
layout: this.props.layout,
config: this.props.config,
frames: this.props.frames,
});
})
.then(() => this.syncWindowResize(null, true))
.then(() => this.syncWindowResize(shouldInvokeResizeHandler))
.then(this.syncEventHandlers)
.then(this.attachUpdateEvents)
.then(() => this.figureCallback(this.props.onInitialized))
.then(() => this.figureCallback(figureCallbackFunction))
.then(shouldAttachUpdateEvents ? this.attachUpdateEvents : () => {})
.catch(err => {
if (err.reason === 'unmounting') {
return;
Expand All @@ -100,59 +99,35 @@ export default function plotComponentFactory(Plotly) {
});
}

UNSAFE_componentWillUpdate(nextProps) {
componentDidMount() {
this.unmounting = false;

this.updatePlotly(true, this.props.onInitialized, true);
}

componentDidUpdate(prevProps) {
this.unmounting = false;

// frames *always* changes identity so fall back to check length only :(
const numPrevFrames =
this.props.frames && this.props.frames.length ? this.props.frames.length : 0;
prevProps.frames && prevProps.frames.length ? prevProps.frames.length : 0;
const numNextFrames =
nextProps.frames && nextProps.frames.length ? nextProps.frames.length : 0;
this.props.frames && this.props.frames.length ? this.props.frames.length : 0;

const figureChanged = !(
nextProps.layout === this.props.layout &&
nextProps.data === this.props.data &&
nextProps.config === this.props.config &&
prevProps.layout === this.props.layout &&
prevProps.data === this.props.data &&
prevProps.config === this.props.config &&
numNextFrames === numPrevFrames
);
const revisionDefined = nextProps.revision !== void 0;
const revisionChanged = nextProps.revision !== this.props.revision;
const revisionDefined = prevProps.revision !== void 0;
const revisionChanged = prevProps.revision !== this.props.revision;

if (!figureChanged && (!revisionDefined || (revisionDefined && !revisionChanged))) {
return;
}

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,
config: nextProps.config,
frames: nextProps.frames,
});
})
.then(() => this.syncEventHandlers(nextProps))
.then(() => this.syncWindowResize(nextProps))
.then(() => this.figureCallback(nextProps.onUpdate))
.catch(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);
}
});
this.updatePlotly(false, this.props.onUpdate, false);
}

componentWillUnmount() {
Expand All @@ -175,19 +150,19 @@ export default function plotComponentFactory(Plotly) {
return;
}

for (let i = 0; i < updateEvents.length; i++) {
this.el.on(updateEvents[i], this.handleUpdate);
}
updateEvents.forEach(updateEvent => {
this.el.on(updateEvent, this.handleUpdate);
});
}

removeUpdateEvents() {
if (!this.el || !this.el.removeListener) {
return;
}

for (let i = 0; i < updateEvents.length; i++) {
this.el.removeListener(updateEvents[i], this.handleUpdate);
}
updateEvents.forEach(updateEvent => {
this.el.removeListener(updateEvent, this.handleUpdate);
});
}

handleUpdate() {
Expand All @@ -203,21 +178,18 @@ export default function plotComponentFactory(Plotly) {
}
}

syncWindowResize(propsIn, invoke) {
const props = propsIn || this.props;
syncWindowResize(invoke) {
if (!isBrowser) {
return;
}

if (props.useResizeHandler && !this.resizeHandler) {
this.resizeHandler = () => {
return Plotly.Plots.resize(this.el);
};
if (this.props.useResizeHandler && !this.resizeHandler) {
this.resizeHandler = () => Plotly.Plots.resize(this.el);
window.addEventListener('resize', this.resizeHandler);
if (invoke) {
this.resizeHandler();
}
} else if (!props.useResizeHandler && this.resizeHandler) {
} else if (!this.props.useResizeHandler && this.resizeHandler) {
window.removeEventListener('resize', this.resizeHandler);
this.resizeHandler = null;
}
Expand All @@ -232,13 +204,9 @@ export default function plotComponentFactory(Plotly) {
}

// Attach and remove event handlers as they're added or removed from props:
syncEventHandlers(propsIn) {
// Allow use of nextProps if passed explicitly:
const props = propsIn || this.props;

for (let i = 0; i < eventNames.length; i++) {
const eventName = eventNames[i];
const prop = props['on' + eventName];
syncEventHandlers() {
eventNames.forEach(eventName => {
const prop = this.props['on' + eventName];
const hasHandler = Boolean(this.handlers[eventName]);

if (prop && !hasHandler) {
Expand All @@ -249,7 +217,7 @@ export default function plotComponentFactory(Plotly) {
this.el.removeListener('plotly_' + eventName.toLowerCase(), this.handlers[eventName]);
delete this.handlers[eventName];
}
}
});
}

render() {
Expand Down Expand Up @@ -281,9 +249,9 @@ export default function plotComponentFactory(Plotly) {
divId: PropTypes.string,
};

for (let i = 0; i < eventNames.length; i++) {
PlotlyComponent.propTypes['on' + eventNames[i]] = PropTypes.func;
}
eventNames.forEach(eventName => {
PlotlyComponent.propTypes['on' + eventName] = PropTypes.func;
});

PlotlyComponent.defaultProps = {
debug: false,
Expand Down