-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Conversation
src/components/GraphiQL.js
Outdated
} | ||
|
||
return null; |
There was a problem hiding this comment.
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
src/components/GraphiQL.js
Outdated
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) { |
There was a problem hiding this comment.
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
src/components/GraphiQL.js
Outdated
@@ -678,7 +689,11 @@ export class GraphiQL extends React.Component { | |||
operationName, | |||
...queryFacts | |||
}); | |||
|
|||
return operationName; |
There was a problem hiding this comment.
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.
src/components/GraphiQL.js
Outdated
@@ -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); |
There was a problem hiding this comment.
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.
a452374
to
e28f98c
Compare
@leebyron applied the changes! |
src/components/GraphiQL.js
Outdated
if (updatedQueryAttributes !== undefined) { | ||
nextOperationName = updatedQueryAttributes.operationName; | ||
|
||
this.setState({ ...updatedQueryAttributes.queryFacts }); |
There was a problem hiding this comment.
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
src/components/GraphiQL.js
Outdated
if (queryFacts) { | ||
// Update operation name should any query names change. | ||
const operationName = getSelectedOperationName( | ||
const updatedOperationName = getSelectedOperationName( | ||
this.state.operations, |
There was a problem hiding this comment.
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.
src/components/GraphiQL.js
Outdated
} | ||
|
||
this.setState({ | ||
operationName, | ||
...queryFacts |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
68b5fbe
to
e1cce42
Compare
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
e1cce42
to
8b83e94
Compare
this.state.operations, | ||
this.state.operationName, | ||
const updatedOperationName = getSelectedOperationName( | ||
operations, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
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
})
I'll send a follow-up PR |
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
andcomponentWillReceiveProps
trying to updateoperationName
by calling thesetState
method, most often times the one incomponentWillReceiveProps
winning incorrectly.The fix is to treat the outcome from
_updateQueryFacts
as truth and properly updating what to update during the component lifecycle.