Skip to content
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

handle the race condition in updating operationName #344

Merged
merged 1 commit into from
Feb 27, 2017

Conversation

asiandrummer
Copy link
Contributor

@asiandrummer asiandrummer commented Feb 23, 2017

If a new query string is passed in (and componentWillReceiveProps is called) without manually editing query (e.g. programmatically modifying the content within the editor without key events), it's possible to have both _updateQueryFacts and componentWillReceiveProps trying to update operationName by calling the setState method, most often times the one in componentWillReceiveProps winning incorrectly.

The fix is to treat the outcome from _updateQueryFacts as truth and properly updating what to update during the component lifecycle.

}

return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

This line isn't necessary, by default returning undefined will behave as expected

const newOperationName = this._updateQueryFacts(value);
// If `operationName` is updated as a result from updating query facts,
// treat it as a source of truth and update here accordingly.
if (newOperationName && newOperationName !== operationName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is repeating the logic on lines 684 below - that seems like this could introduce more possibilities for race conditions or split logic

@@ -678,7 +689,11 @@ export class GraphiQL extends React.Component {
operationName,
...queryFacts
});

return operationName;
Copy link
Contributor

Choose a reason for hiding this comment

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

What if instead of calling setState directly, this function returned the object. That way you could replace calls to this._updateQueryFacts(query) with this.setState(this._updateQueryFacts(query)), however if you expect that multiple setState calls will occur in a row, then you can merge them before calling setState.

Another root issue seems to be that this function uses this.state as the source of truth, however clearly there are cases where operationName should not be read from this.state and instead should be passed into the function. I would suggest accepting a 2nd argument for operationName and using that instead of this.state. operationName within this function.

@@ -167,7 +167,12 @@ export class GraphiQL extends React.Component {
if (nextSchema !== this.state.schema ||
nextQuery !== this.state.query ||
nextOperationName !== this.state.operationName) {
this._updateQueryFacts(nextQuery);
const newOperationName = this._updateQueryFacts(nextQuery);
Copy link
Contributor

Choose a reason for hiding this comment

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

Notice that schema can also differ from what is in this.state which will also cause a race condition. schema should also be provided as an argument to _updateQueryFacts to avoid this.

@asiandrummer asiandrummer force-pushed the asiandrummer-patch-2 branch 2 times, most recently from a452374 to e28f98c Compare February 24, 2017 02:44
@asiandrummer
Copy link
Contributor Author

asiandrummer commented Feb 24, 2017

@leebyron applied the changes!

if (updatedQueryAttributes !== undefined) {
nextOperationName = updatedQueryAttributes.operationName;

this.setState({ ...updatedQueryAttributes.queryFacts });
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't this be this.setState(updatedQueryAttributes)? .queryFacts isn't a property set anywhere, the variable by that name is spread into the return value

if (queryFacts) {
// Update operation name should any query names change.
const operationName = getSelectedOperationName(
const updatedOperationName = getSelectedOperationName(
this.state.operations,
Copy link
Contributor

Choose a reason for hiding this comment

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

This line should be queryFacts.operations shouldn't it... Relying on this.state.operations assumes the facts from the previous query.

}

this.setState({
operationName,
...queryFacts
Copy link
Contributor Author

Choose a reason for hiding this comment

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

shouldn't this be this.setState(updatedQueryAttributes)?

We were destructuring that object in here - we could probably be more specific with what we're setting the state with.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes exactly. Since it's spread in here, updatedQueryAttributes.queryFacts above won't exist, updatedQueryAttributes will contain the data spread in from the queryFacts var here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oi I see what you mean - sorry! Changed.

@asiandrummer asiandrummer force-pushed the asiandrummer-patch-2 branch 2 times, most recently from 68b5fbe to e1cce42 Compare February 27, 2017 21:47
If a new query string is passed in (and `componentWillReceiveProps` is called) without manually editing query (e.g. programmatically modifying the content within the editor without key events), it's possible to have both `_updateQueryFacts` and `componentWillReceiveProps` tryint to update `operationName` by calling the `setState` method, most often times the one in `componentWillReceiveProps` winning incorrectly. The fix is to treat the outcome from `_updateQueryFacts` as truth and properly updating what to update during the component lifecycle.

be more specific when getting object properties
@asiandrummer asiandrummer merged commit 2fbde0c into master Feb 27, 2017
@asiandrummer asiandrummer deleted the asiandrummer-patch-2 branch February 27, 2017 22:12
this.state.operations,
this.state.operationName,
const updatedOperationName = getSelectedOperationName(
operations,
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be queryFacts.operations - the operations passed in from the outside (via this.state.operations) will be derived from old information. getQueryFacts is responsible for determining that value from the query

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I'm wrong on this one. This is the previous operations, just was confused by the names

this.state.operations,
this.state.schema,
),
);
}
this.setState({ query: value });
Copy link
Contributor

Choose a reason for hiding this comment

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

Combine this with the above to ensure one call to setState?

const queryFacts = this.updateQueryFacts(...);
this.setState({
  query: value,
  ...queryFacts
})

@leebyron
Copy link
Contributor

I'll send a follow-up PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants