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

CRS-292 Prevent state updates on unmounted Channel component #566

Merged
merged 3 commits into from
Oct 14, 2020

Conversation

ath92
Copy link

@ath92 ath92 commented Oct 9, 2020

Description of the pull request

This prevents React from logging a warning to the console if the Channel component finishes its loadMore callback after it has already been unmounted.

Steps to reproduce:

  1. Trigger Channel's loadMore function
  2. Unmount Channel component (e.g. by switching to a different channel) while loadMore is still fetching
  3. When the request resolves, Channel will try to call dispatch on its state reducer, even though that's a noop. React complains with a 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 a useEffect cleanup function.
in ChannelInner (created by Channel)

Note: I spent quite some time trying to write a test case for this, but it seems that it's very hard to figure out if a component tries to update its state after it has been unmounted. For example, React prevents the actual reducer from being called, so you cannot just spy on that to know if there was an attempted state update.

… state update if component has already been unmounted.
@github-actions
Copy link

github-actions bot commented Oct 9, 2020

Size Change: +264 B (0%)

Total Size: 1.25 MB

Filename Size Change
./dist/browser.full-bundle.js 639 kB +67 B (0%)
./dist/browser.full-bundle.min.js 371 kB +38 B (0%)
./dist/index.es.js 104 kB +84 B (0%)
./dist/index.js 106 kB +75 B (0%)
ℹ️ View Unchanged
Filename Size Change
./dist/css/index.css 22.9 kB 0 B
./dist/css/index.js 21 B 0 B
./dist/i18n/en.json 790 B 0 B
./dist/i18n/fr.json 1.21 kB 0 B
./dist/i18n/hi.json 1.37 kB 0 B
./dist/i18n/it.json 1.14 kB 0 B
./dist/i18n/nl.json 1.12 kB 0 B
./dist/i18n/ru.json 1.4 kB 0 B
./dist/i18n/tr.json 1.14 kB 0 B

compressed-size-action

Copy link
Contributor

@jaapbakker88 jaapbakker88 left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -138,6 +138,7 @@ const ChannelInner = ({
const lastRead = useRef(new Date());
const chatContext = useContext(ChatContext);
const online = useRef(true);
const isMounted = useRef(true);
Copy link
Contributor

@vishalnarkhede vishalnarkhede Oct 12, 2020

Choose a reason for hiding this comment

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

I have seen this weird issue with our previous class based components as well. LGTM

@ath92 Maybe can we extract it to separate hook?

const isMounted = useIsMounted();

Copy link
Author

Choose a reason for hiding this comment

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

I suppose this hook would basically be a combination of useRef for storing the flag + useEffect for setting it to true/false?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah

Copy link
Author

Choose a reason for hiding this comment

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

Just did this, also allowed me to write a test quite easily :)

@vini-btc
Copy link
Contributor

Would be nice if we could also get rid of the root cause of the warning. In this case, cancel the promise or abort the request.

@ath92
Copy link
Author

ath92 commented Oct 13, 2020

Would be nice if we could also get rid of the root cause of the warning. In this case, cancel the promise or abort the request.

It seems stream-chat-js gives access to the axios instance, and lets me pass options into the actual axios call used by loadMore, so I think it should be possible to cancel the request that way.

But I don't think that would actually get rid of state updates; axios would reject its Promise, and we'd end up in the catch block where there's another state update.

I think in this case cancelling the request will add little benefit: it would require additional stateful logic (i.e. we'd have to keep the axios cancelToken somewhere, and make sure the request actually gets cancelled on unmount). If the server handles cancellation requests, I think in most cases this request would arrive at the server too late to make a difference.

@jaapbakker88
Copy link
Contributor

@ath92 think this will do for now, let's get this merged and released.

@mahboubii mahboubii merged commit 6de4b41 into master Oct 14, 2020
@delete-merged-branch delete-merged-branch bot deleted the CRS-292-Channel-unmounted-dispatch branch October 14, 2020 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants