Graphiql: Warning: Can't perform a React state update on an unmounted component

Created on 28 Apr 2020  路  50Comments  路  Source: graphql/graphiql

I am using GraphiQL in the form as a textarea field and every time I hit submit and navigate away from the page I get this warning

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in GraphiQL (created by GraphQlTextArea)

Can someone please look into this?

bug

Most helpful comment

@sashahavia we released another alpha today that should take care of this, by avoiding fetch requests during re-renders. [email protected] should be the one you want

let me know if that helps! if not, we will try the other fix as well.

@acao Getting this error when trying to run the app:

ERROR in ./node_modules/graphql-language-service-interface/esm/getAutocompleteSuggestions.js 35:51
Module parse failed: Unexpected token (35:51)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|                 label: argDef.name,
|                 detail: String(argDef.type),
>                 documentation: argDef.description ?? undefined,
|                 kind: CompletionItemKind.Variable,
|             })));
 @ ./node_modules/graphql-language-service-interface/esm/index.js 2:0-45 2:0-45
 @ ./node_modules/codemirror-graphql/hint.js
 @ ./node_modules/graphiql/esm/components/QueryEditor.js
 @ ./node_modules/graphiql/esm/components/GraphiQL.js
 @ ./node_modules/graphiql/esm/index.js

All 50 comments

sounds like it might be an issue with the current usage of componentWillIUnmount. what version of graphiql and react are you using? if you update to the 1.0.0-alpha.n versions of GraphiQL, we use react 16 and hooks and start to replace some of those methods.

if that doesn't help, we are in the midst of a rewrite, so after this PR is merged that lifecycle method won't be used at all anymore:

https://github.com/graphql/graphiql/pull/1468

I am using graphiql version 0.17.5 and react version 16.8.6

Ok, I will try updating to the 1.0.0-alpha.n versions

ok yeah, lemme know! that should be the most progressive path. if that doesn鈥檛 work, graphiql 0.x.y versions seem to work best with react 15

I tried 1.0.0-alpha.8 version of graphiql and still getting the error. Unfortunately can't switch to react 15. Looking forward to the rewrite.

@sashahavia yeah it's all still there:
https://github.com/graphql/graphiql/blob/master/packages/graphiql/src/components/GraphiQL.tsx#L351

our bad, we should have removed this a while ago. I think in react 16 at some minor version along the way they introduced it. don't usually see this because i don't test with any examples that programatically mount/unmount, GraphiQL is usually the root component in all of our examples!

if you fork master branch, you could remove the entire componentWillUnmount, or I can just kick out another alpha version with that removed, is that ok?

this is why the upgrade to react 16 was still in alpha... I knew there'd be issues like this! thanks so much for raising it. I think I'll just make another quick alpha release to fix this before we merge the rewrite

If you can make another alpha release to fix that that would be great

@sashahavia i'm still having trouble reproducing the issue, can you provide the steps to produce this bug? perhaps you can document the bug by forking this codesandbox?

The error happens only when you introduce useEffect

useEffect(() => {
    props.getEndpoints();
}, []);

when you useEffect in a parent component? props.getEndpoints() - this is something specific to your implementation, right? let me see if i can reproduce so i can fix it, thanks for your patience!

Here's a demo :
https://codesandbox.io/s/graphiql-js-next-example-qsh7h?file=/src/index.js

seems to be working ok with a basic console.log()

so possibly your props.getEndpoints() is doing something that triggers a bug that still may be on our side? does it write to state or use context or anything like that?

Yes, when I do console.log() in my implementation I also don't get an error

It only happens when I actually call my redux action to get all endpoints. My action makes an axios request

Not sure if this helps but here is the full stack trace of the warning

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
    in GraphiQL (created by GraphQlStatement)
    in div (created by GraphQlStatement)

r | @ | react_devtools_backend.js:6
-- | -- | --
聽 | warningWithoutStack | @ | vendors~main.bundle.js:114722
聽 | warnAboutUpdateOnUnmounted | @ | vendors~main.bundle.js:132691
聽 | scheduleWork | @ | vendors~main.bundle.js:134101
聽 | enqueueSetState | @ | vendors~main.bundle.js:125385
聽 | push.../../node_modules/react/cjs/react.development.js.Component.setState | @ | vendors~main.bundle.js:155038
聽 | (anonymous) | @ | vendors~main.bundle.js:50653
聽 | Promise.then (async) | 聽 | 聽
聽 | push.../../node_modules/graphiql/esm/components/GraphiQL.js.GraphiQL.fetchSchema | @ | vendors~main.bundle.js:50646
聽 | push.../../node_modules/graphiql/esm/components/GraphiQL.js.GraphiQL.componentDidMount | @ | vendors~main.bundle.js:50405
聽 | commitLifeCycles | @ | vendors~main.bundle.js:131550
聽 | commitAllLifeCycles | @ | vendors~main.bundle.js:132952
聽 | callCallback | @ | vendors~main.bundle.js:114365
聽 | invokeGuardedCallbackDev | @ | vendors~main.bundle.js:114415
聽 | invokeGuardedCallback | @ | vendors~main.bundle.js:114472
聽 | commitRoot | @ | vendors~main.bundle.js:133164
聽 | (anonymous) | @ | vendors~main.bundle.js:134634
聽 | unstable_runWithPriority | @ | vendors~main.bundle.js:168544
聽 | completeRoot | @ | vendors~main.bundle.js:134633
聽 | performWorkOnRoot | @ | vendors~main.bundle.js:134562
聽 | performWork | @ | vendors~main.bundle.js:134470
聽 | performSyncWork | @ | vendors~main.bundle.js:134444
聽 | requestWork | @ | vendors~main.bundle.js:134313
聽 | scheduleWork | @ | vendors~main.bundle.js:134127
聽 | scheduleRootUpdate | @ | vendors~main.bundle.js:134788
聽 | updateContainerAtExpirationTime | @ | vendors~main.bundle.js:134816
聽 | updateContainer | @ | vendors~main.bundle.js:134873
聽 | push.../../node_modules/react-dom/cjs/react-dom.development.js.ReactRoot.render | @ | vendors~main.bundle.js:135169
聽 | (anonymous) | @ | vendors~main.bundle.js:135306
聽 | unbatchedUpdates | @ | vendors~main.bundle.js:134675
聽 | legacyRenderSubtreeIntoContainer | @ | vendors~main.bundle.js:135302
聽 | render | @ | vendors~main.bundle.js:135371
聽 | ./main.js | @ | main.bundle.js:23273
聽 | __webpack_require__ | @ | main.bundle.js:785
聽 | fn | @ | main.bundle.js:151
聽 | 0 | @ | main.bundle.js:32553
聽 | __webpack_require__ | @ | main.bundle.js:785
聽 | checkDeferredModules | @ | main.bundle.js:46
聽 | (anonymous) | @ | main.bundle.js:861
聽 | (anonymous) | @ | main.bundle.js:864


hmm, trying to emulate what may be happening and I still don't quite see it - maybe it's re-mounting because of multiple incoming props changes, which would invoke willUmount, and my guess is this is happening just as redux is performing it's async fetch operation. It seems in React 16 they have safegaurds against this that weren't there for the old react 15 lifecycle, which is why we are just seeing this with the new react hooks components? Just a theory. Here's my attempt:

https://codesandbox.io/s/graphiql-js-next-example-qsh7h?file=/src/index.js

i have an idea for a quick potential hack though, but I cannot test it because I cannot reproduce this.

try this beneath where you import GraphiQL component:

GraphiQL.prototype.componentWillUnmount = undefined;

(when i try that it runs fine)

I think maybe this indeed is the culprit as suspected, and I could remove the method and re-release without being able to test myself, but this would allow you to test much more quickly

@sashahavia did you have any luck with that?

Just want to chase down this bug as it seems to be the only bug stopping us from our stable release for 1.0.0, and then merge a bunch of breaking changes we have waiting.

I don't think I'll be able to reproduce this bug it seems like it's timing related, so that line above is what I suspect will prove that we just need to remove that entire lifecycle method

Looks like GraphiQL.prototype.componentWillUnmount = undefined; helped

Great! ok, i'll cut an alpha release today for you to try out. Didn't want to remove that functionality unless it fixed this issue. Thanks a bunch for helping me track it down!

hmm, looks like this fix will break more functionality than I had thought, particularly with persisting ui state. this is the only place many of these values are set. is that temporary fix O.K. until I can spend some more time on this and cut another alpha later this week?

No worries. I can wait. It doesn't break the functionality for me, it is just a warning. And by the way GraphiQL.prototype.componentWillUnmount = undefined; - the warning still pops up but not as often

I was thinking is it possible to add support for canceling all requests. I think that's what actually breaking.

I am using axios in my fetcher and it would be nice if GraphiQL could accept cancel function and then call it in componentWillUnmount

const source = axios.CancelToken.source();

And then in GraphiQL

<GraphiQL ref={c => graphiql = c} fetcher={createFetcher(api)} query={query} onEditQuery={() => updateQuery()} cancel={source.cancel()}>

And for the fetch request we can use

const controller = new AbortController();
const { signal } = controller;

<GraphiQL ref={c => graphiql = c} fetcher={createFetcher(api)} query={query} onEditQuery={() => updateQuery()} cancel={controller.abort()}>

cancel function can be optional parameter for GraphiQL

When I look at the network tab I can see multiple requests being fired at the same time by fetcher function, it would be nice to cancel requests when unMounting since the user can move away from the page before requests are finished

cancellable operation HTTP requests and WSS subscriptions sound fantastic. this could be added to the default fetcher in the rewrite we are about to merge that will replace a lot of this.

the breaking changes from removing componentWillMount in terms of persisting UI state will noticeably impact other users and implementations unfortunately, so we will still need to persist that state, and I will do so by using a more appropriate place in the lifecycle to persist state.

also worth noting these changes will all be discarded for the 2.0.0 rewrite, as we are changing how we persist state.

if you think the cancellable requests solve it, can you get it working without setting componentWillMount to undefined?

a fetcher should not be making multiple requests each time a request is fired, that sounds like an implementation issue. try here: https://graphiql-test.netlify.app/

In my case multiple requests happen because I am using it within Redux form.

 <Field
                label="GraphQL Statement"
                name="inputJson"
                query={query}
                change={change}
                endpoints={endpoints}
                component={GraphQlTextArea}
            />

GraphQlTextArea has GraphiQL with fetcher implementation

thank you! very helpful to know

for a contributor, steps to fix this bug while not breaking features are:

  1. use master branch
  2. move all functionality in GraphiQL.componentWillUnmount to the respective events
  3. for events that use event listeners, use our existing debounce logic - for example, a drag event may trigger 100 times, but we only need to persist once, a few seconds after the user has completed dragging.

@sashahavia we released another alpha today that should take care of this, by avoiding fetch requests during re-renders. [email protected] should be the one you want

let me know if that helps! if not, we will try the other fix as well.

@acao I didn't have a chance to test it this week. Will try to test it first thing Monday.

@sashahavia we released another alpha today that should take care of this, by avoiding fetch requests during re-renders. [email protected] should be the one you want

let me know if that helps! if not, we will try the other fix as well.

@acao Getting this error when trying to run the app:

ERROR in ./node_modules/graphql-language-service-interface/esm/getAutocompleteSuggestions.js 35:51
Module parse failed: Unexpected token (35:51)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|                 label: argDef.name,
|                 detail: String(argDef.type),
>                 documentation: argDef.description ?? undefined,
|                 kind: CompletionItemKind.Variable,
|             })));
 @ ./node_modules/graphql-language-service-interface/esm/index.js 2:0-45 2:0-45
 @ ./node_modules/codemirror-graphql/hint.js
 @ ./node_modules/graphiql/esm/components/QueryEditor.js
 @ ./node_modules/graphiql/esm/components/GraphiQL.js
 @ ./node_modules/graphiql/esm/index.js

oh weird! that package should be compiling to es5 now. that was from another recent change

I can confirm that [email protected] breaks the webpack compilation.

You can add a resolution in your package.json until it's fixed:

"resolutions": {
  "graphql-language-service-interface": "2.4.0-alpha.8"
}

@zoontek Getting these errors now

ERROR in ./node_modules/codemirror-graphql/lint.js
Module not found: Error: Can't resolve 'graphql-language-service-interface' in '/.../.../Desktop/.../.../node_modules/codemirror-graphql'
 @ ./node_modules/codemirror-graphql/lint.js 5:39-84
 @ ./node_modules/graphiql/esm/components/QueryEditor.js
 @ ./node_modules/graphiql/esm/components/GraphiQL.js
 @ ./node_modules/graphiql/esm/index.js
...
 @ multi ./main.js webpack-hot-middleware/client

ERROR in ./node_modules/codemirror-graphql/hint.js
Module not found: Error: Can't resolve 'graphql-language-service-interface' in '/.../.../Desktop/.../.../node_modules/codemirror-graphql'
 @ ./node_modules/codemirror-graphql/hint.js 5:39-84
 @ ./node_modules/graphiql/esm/components/QueryEditor.js
 @ ./node_modules/graphiql/esm/components/GraphiQL.js
 @ ./node_modules/graphiql/esm/index.js
...
 @ multi ./main.js webpack-hot-middleware/client

i can't seem to reproduce this bug with our webpack example in master (see webpack config and babel config)

so i will go ahead and publish again from master.

@sashahavia is this still an issue with the latest alpha.13 release?

@acao alpha.13 compiled successfully

Unfortunately, I am still seeing the error

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

@sashahavia ok! the PR that we thought might fix it had that same error, but I think we had just reproduced it in a different context. gonnna try this other PR, #1544. thanks for helping with this!

@sashahavia apologies this closed automatically. do you want to see if the 1.0.0 release resolves all of these issues for you?

@acao I have updated to 1.0.0 release and still got this error

react_devtools_backend.js:6 Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

but we got rid of componentWillUnmount :crying_cat_face:

i'll work on a reproduction of this for tomorrow. 2.0.0 redesign and plugin api on hold! need to get 1.0.x right

are you using componentWillUnmount anywhere by chance?

@acao We are suing React hooks and I don't have unmount hook in any of the components that I am using graphiql

we have several other componentWillUnmount methods in other components, primarily to remove event listeners. network cancellation didn't seem to address it either (cancelling schema fetch on unmount). the contributor who made the PR for network cancellation was able to reproduce it, so I'm seeing if he can help me figure it out, as i spent most of last night and this morning trying to reproduce this bug.

thinking about it more though... _if_ it is related to the schema fetch, one workaround you might try is fetching the schema manually from elsewhere in the component tree, and passing it to <GraphiQL schema={schema} /> if you aren't already doing that. Then, it won't try to fetch the schema on mount on every "hard" re-render.

@sashahavia let me know how that workaround goes, and [email protected] has a fix that may help you as well, fwiw

@acao The workaround I have tried originally and it didn't help. I just tried [email protected] and still getting the same error. I also tried the workaround with the new version [email protected] and still getting the same error

@sashahavia the original case where someone was able to re-produce a similiar error was resolved, so without a way to re-produce the errors, we're kinda stuck. if you can create a codesandbox that re-creates the error then we should be able to help you.

@zoontek i see you are subscribing to this issue. are you having a similar issue?

@acao Hey, sorry it took me so long. Here is the link to sandbox to re-produce the error

https://codesandbox.io/s/goofy-brook-kvren

fanatstic, was able to reproduce with this example, thank you!

now i can't reproduce it again, hahaha! what are the exact steps with this demo?

@acao type something in Graphiql, add name and click submit. The error sometimes a little delayed. But I could see it both in sandbox console and chrome console tab

ok got it! i was doing the first two backwards. i also see that the active query goes blank once the typing begins! maybe a few bugs exposed here. thank you

ok got it! i was doing the first two backwards. i also see that the active query goes blank once the typing begins! maybe a few bugs exposed here. thank you

Hey @acao,

I found why the active query goes blank when we start typing, that's because GraphiQL is passed with the query prop which is equal to "".

<GraphiQL ref={c => (graphiql = c)} fetcher={createFetcher(api)} query="">

@harshithpabbati yes I'm aware, thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dontsave picture dontsave  路  19Comments

orta picture orta  路  10Comments

OlegIlyenko picture OlegIlyenko  路  17Comments

divyenduz picture divyenduz  路  16Comments

ElijahLynn picture ElijahLynn  路  10Comments