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

Async state hangs on forever in Safari #165

Closed
mitas1 opened this issue Apr 16, 2021 · 14 comments
Closed

Async state hangs on forever in Safari #165

mitas1 opened this issue Apr 16, 2021 · 14 comments
Labels
bug Something isn't working

Comments

@mitas1
Copy link

mitas1 commented Apr 16, 2021

I'm hitting some really weird bug with:

  • Safari 14.0.3
  • Mac OS 11.2.3 (20D91) M1 chip

I've created a reproducable example here: mitas1@d3649bb

Steps to reproduce

I'm trying to load the state in an async way. I'm calling just a dummy fetch see my example. After I will reload the Safari, it will just show the loading state forever. This is some kind of race conditioning bcs. sometimes it works.

I can hit this almost every time I'm reloading the Safari. It works only if I will clear the cache.

My theory is that, maybe due to some cool Safari caching or whatever, if the promise is resolved too soon it will not rerender the state because there is nothing subscribed (cannot be updated) yet.

@avkonst
Copy link
Owner

avkonst commented Apr 16, 2021

This is sad, but I am not sure how to progress it further. I do not have Safari at hands to try and debug. I can assist you debugging. Have you tried to put some logging to the app code (before and after await, in render component)? Maybe you need to instrument hookstate API function with more debugging to see what is actually called and what events are fired. And compare it with Chrome for example. Also is the sample from documentation works on safari?

@avkonst avkonst added the bug Something isn't working label Apr 16, 2021
@mitas1
Copy link
Author

mitas1 commented Apr 17, 2021

I was trying to debug this. When I get this issue I sow this._subscribers (https://github.com/avkonst/hookstate/blob/master/src/index.ts#L946) being an empty Set. When it works I do have a 2 things subscribed and being updated.

The example from the documentation works fine in Safari. But I'm afraid that if you will use fetch it will not going to work properly in Safari.

When I clear the cache If I will just reload
Screenshot 2021-04-17 at 09 54 25 Screenshot 2021-04-17 at 09 53 23

@avkonst
Copy link
Owner

avkonst commented Apr 17, 2021

"reload" is page refresh, ala F5?

@avkonst
Copy link
Owner

avkonst commented Apr 17, 2021

Empty subscribers list means the 2 components actually refer to another variable, ie. are not rendered on page reload, but the global variable is reset to initial. I do not know how it is possible. You could try to check if a state object, the result/return of useState, still refers to the same Store object (the one you) as the global one. Mounted / Hooked states should refer somewhere, I wonder if it is the same state after reload or there are 2 different instances acting here

@mitas1
Copy link
Author

mitas1 commented Apr 17, 2021

Yes the "reload" means page refresh F5.

Well, the point is that the subscriber list is empty in time of update is fired but later there are components subscribed to the Store. I've put the console.log into the update(https://github.com/avkonst/hookstate/blob/master/src/index.ts#L938) and subscribe(https://github.com/avkonst/hookstate/blob/master/src/index.ts#L1035) functions.

Here we can see in the first example that the components are being subscribed before update is being called and in the second the components are subscribed after the update is called.

When I clear the cache If I will just refresh the page
Screenshot 2021-04-17 at 10 57 14 Screenshot 2021-04-17 at 10 57 20

How should I check if the state is the same instance? I'm not sure if I understood.

@avkonst
Copy link
Owner

avkonst commented Apr 18, 2021

In the first one there are 2 update calls. Is the second one when the promise is resolved?
In the second there is only one update, when a state is set to the initial value (none), I believe. There is no second update. Is the async function ever returning / completing?

@mitas1
Copy link
Author

mitas1 commented Apr 19, 2021

Why do you think in the first one there are 2 update calls? I think in both cases the update is fired only once and in both cases the update is fired AFTER the promise is resolved. I've added log right before the promise is returning the value and it gets printed before the update in both scenarios.

@avkonst
Copy link
Owner

avkonst commented Apr 19, 2021

What does this (2) label mean?
image

@avkonst
Copy link
Owner

avkonst commented Apr 19, 2021

It is a real puzzle.

"How should I check if the state is the same instance? I'm not sure if I understood."

I was thinking to add some random value property to the Store object. Then check if update event is fired by the same instance of Store as the one the components are subscribing to... Would you be able to check it?

I run out of ideas. I think the best is to plan some zoom session and debug it together. I am in New Zealand time zone and can assist in the evening after 8pm, for example

@mitas1
Copy link
Author

mitas1 commented Apr 20, 2021

Label (2) means that the set has two elements. I will try that random number in state. Perfect 8.p.m. your time works fine for me. So how can I reach you? You can send me DM to twitter.

@mitas1
Copy link
Author

mitas1 commented Apr 20, 2021

I've added a new private property to state which is being initialized in Store.constructor from the global variable. After the initialization the global variable is increased by 1. Later I'm priting the private id property.

When I clear the cache If I will just refresh the page
Screenshot 2021-04-20 at 10 11 27 Screenshot 2021-04-20 at 10 10 13

@avkonst
Copy link
Owner

avkonst commented Apr 21, 2021

I can do today in the evening 8pm. Email to me to arrange hangouts meeting: avkonst@gmail.com

@avkonst
Copy link
Owner

avkonst commented Apr 22, 2021

Thanks for debugging with me on the meeting. Here is the summary:

Sequence of events when a bug is reproduced:

  • Rendering of a component is finished. State object captures state with edition 0 and value none
  • React releases control for the browser to paint
  • Browser does not finish painting but dispatches network event which resolves the promised state, which triggers the Store.update call but without any subscribers.
  • React invokes effect callback from useSubscribedStateMethods, which places the subscription to Store.update and it is too late.

The verified solution: replace useEffect with useLayoutEffect fixes the problem. It forces react to place the subscription before the control is released to the browser.

@avkonst
Copy link
Owner

avkonst commented Jun 8, 2021

Fixed in 3.0.8

@avkonst avkonst closed this as completed Jun 8, 2021
shinyjohn0401 pushed a commit to shinyjohn0401/hookstate that referenced this issue Jan 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants