Skip to content

Commit

Permalink
Clear dispatch token
Browse files Browse the repository at this point in the history
It seems that in certain versions of React it was possible for
`componentWillMount` to be called more than once. facebook/react#6613

If that happened to a `connect`ed component, it would cause
`dispatcher.unregister` to be called again with a token that was already
unregistered causing an exception.

This `null`s the token field so unregister can only be called once for a
given token.
  • Loading branch information
Colby Rabideau committed Mar 6, 2017
1 parent e16326d commit 6fe0eb6
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 60 deletions.
53 changes: 20 additions & 33 deletions src/dependencies/StoreDependencyMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@ import { enforceDispatcher } from '../dispatcher/DispatcherInterface';
import invariant from 'invariant';

type ReactMixin = {
__dispatchToken?: string;
propTypes?: Object;
componentWillMount: Function;
componentWillReceiveProps: Function;
componentDidUpdate?: Function;
componentWillUnmount: Function;
}
__dispatchToken?: string,
propTypes?: Object,
componentWillMount: Function,
componentWillReceiveProps: Function,
componentDidUpdate?: Function,
componentWillUnmount: Function,
};

function onlyStoreStateChanged(dependencies, state, prevState): bool {
function onlyStoreStateChanged(dependencies, state, prevState): boolean {
for (const field in state) {
if (
!dependencies.hasOwnProperty(field) &&
state[field] !== prevState[field]
) {
if (!dependencies.hasOwnProperty(field) && state[field] !== prevState[field]) {
return false;
}
}
Expand All @@ -38,7 +35,7 @@ function onlyStoreStateChanged(dependencies, state, prevState): bool {

export default function StoreDependencyMixin(
dependencies: DependencyMap,
dispatcher: ?Dispatcher = DispatcherInstance.get()
dispatcher: ?Dispatcher = DispatcherInstance.get(),
): ReactMixin {
enforceDispatcher(dispatcher);

Expand All @@ -53,46 +50,36 @@ export default function StoreDependencyMixin(
invariant(
this.__dispatchToken === undefined,
'Only one `StoreDependencyMixin` may be registered on `%s`',
this.constructor.displayName
this.constructor.displayName,
);
if (dispatcher) {
this.__dispatchToken = dispatcher.register(
handleDispatch.bind(
null,
dispatcher,
dependencyIndex,
(entry) => {
this.setState(
calculateForDispatch(dependencies, entry, this.props, this.state)
);
}
)
handleDispatch.bind(null, dispatcher, dependencyIndex, entry => {
this.setState(calculateForDispatch(dependencies, entry, this.props, this.state));
}),
);
}
},

componentWillReceiveProps(nextProps): void {
this.setState(
calculateForPropsChange(dependencies, nextProps, this.state)
);
this.setState(calculateForPropsChange(dependencies, nextProps, this.state));
},

componentWillUnmount(): void {
if (dispatcher && this.__dispatchToken) {
dispatcher.unregister(this.__dispatchToken);
const dispatchToken = this.__dispatchToken;
if (dispatcher && dispatchToken) {
this.__dispatchToken = null;
dispatcher.unregister(dispatchToken);
}
},

};

if (dependenciesUseState(dependencies)) {
mixin.componentDidUpdate = function componentDidUpdate(_, prevState) {
if (onlyStoreStateChanged(dependencies, this.state, prevState)) {
return;
}
this.setState(
calculateForStateChange(dependencies, this.props, this.state)
);
this.setState(calculateForStateChange(dependencies, this.props, this.state));
};
}

Expand Down
44 changes: 17 additions & 27 deletions src/dependencies/connect.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
/* @flow */
import type {DependencyIndexEntry, DependencyMap} from './DependencyMap';
import type {Dispatcher} from 'flux';
import type { DependencyIndexEntry, DependencyMap } from './DependencyMap';
import type { Dispatcher } from 'flux';
import {
calculateInitial,
calculateForDispatch,
calculateForPropsChange,
dependencyPropTypes,
makeDependencyIndex
makeDependencyIndex,
} from '../dependencies/DependencyMap';
import {handleDispatch} from './Dispatch';
import {get as getDispatcherInstance} from '../dispatcher/DispatcherInstance';
import {enforceDispatcher} from '../dispatcher/DispatcherInterface';
import React, {Component} from 'react';
import { handleDispatch } from './Dispatch';
import { get as getDispatcherInstance } from '../dispatcher/DispatcherInstance';
import { enforceDispatcher } from '../dispatcher/DispatcherInterface';
import React, { Component } from 'react';

function transferStaticProperties(
fromClass: Object,
// By setting the type to Object, I'm doing a little dance around the type
// checker... I fully expect this to break after a future flow upgrade.
toClass: Object
toClass: Object,
) {
Object.keys(fromClass).forEach(staticField => {
toClass[staticField] = fromClass[staticField];
Expand All @@ -26,7 +26,7 @@ function transferStaticProperties(

export default function connect(
dependencies: DependencyMap,
dispatcher: ?Dispatcher = getDispatcherInstance()
dispatcher: ?Dispatcher = getDispatcherInstance(),
): Function {
enforceDispatcher(dispatcher);

Expand All @@ -47,33 +47,26 @@ export default function connect(
componentWillMount() {
if (dispatcher) {
this.dispatchToken = dispatcher.register(
handleDispatch.bind(
null,
dispatcher,
dependencyIndex,
this.handleDispatch.bind(this)
)
handleDispatch.bind(null, dispatcher, dependencyIndex, this.handleDispatch.bind(this)),
);
}
this.setState(calculateInitial(dependencies, this.props, this.state));
}

componentWillReceiveProps(nextProps: Object): void {
this.setState(
calculateForPropsChange(dependencies, nextProps, this.state)
);
this.setState(calculateForPropsChange(dependencies, nextProps, this.state));
}

componentWillUnmount(): void {
if (dispatcher && this.dispatchToken) {
dispatcher.unregister(this.dispatchToken);
const dispatchToken = this.dispatchToken;
if (dispatcher && dispatchToken) {
this.dispatchToken = null;
dispatcher.unregister(dispatchToken);
}
}

handleDispatch(entry: DependencyIndexEntry) {
this.setState(
calculateForDispatch(dependencies, entry, this.props, this.state)
);
this.setState(calculateForDispatch(dependencies, entry, this.props, this.state));
}

render() {
Expand All @@ -83,10 +76,7 @@ export default function connect(

transferStaticProperties(BaseComponent, ConnectedComponent);
ConnectedComponent.displayName = `Connected(${BaseComponent.displayName})`;
ConnectedComponent.propTypes = dependencyPropTypes(
dependencies,
BaseComponent.propTypes
);
ConnectedComponent.propTypes = dependencyPropTypes(dependencies, BaseComponent.propTypes);

return ConnectedComponent;
};
Expand Down

0 comments on commit 6fe0eb6

Please sign in to comment.