-
Notifications
You must be signed in to change notification settings - Fork 233
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
TypeError: Cannot destructure property 'error' of 'results[(results.length - 1)]' as it is undefined. [Suspense] #554
Comments
If something does not suspense is it still undefined? If so this is a really bad change as now one cannot test for suspense. |
Please tell me you set current to some other value if it does not suspend. |
Hi @ntucker, You're correct that the same fix should have been applied to With respect to you other points, I think you are misunderstanding the role of the I'll get a patch out for |
Fix has gone out in version I have also updated the release notes of version |
Ok, now to your other points: The changes were undocumented in 3.6 as they were never meant to affect the functionality of suspending hooks (i.e. it was bug of that version not a breaking change intended to make it version 4.0). The tests that would have caught that break only went out with 5.0 when I identified the issue with "Why was there no test to catch it prior to 3.6?" I hear you ask. Well, prior to 3.6, there was no special handling required to cater for a hook that had not returned a result (or error) in the first pass. In fact, I'd go so far as to say that "seeing" The reason I say that I actually believe So in summary, when the changes were made in 3.6, I believed they were non-breaking as for a non-suspending hook call, the values are effectively internal variables and never get exposed. Obviously I failed to consider the suspending hook cases and a hole in our test suite (I'm aware your test suite did catch the bug) let it through unnoticed. I am sorry about that. I hope that addresses your concerns about non-suspending hooks, the lack of documented breaks and the reasons it broke in the first place. If I have missed anything, please leave a comment and I will do my best to give you address it as well. Finally, I want to address one final thing:
I think you may be conflating your most import use of this library and everyones most important use of this library. Handling suspending hooks is something we support and not something we ever purposely break, but it's only one part of what this library offers. I honestly couldn't tell you what the most important feature we offer is, other than perhaps simply users being able to call a hook without defining a component themselves, as we have many users, each with their own most important use. In fact, until 5.0 we'd been having many people calling out for the ability to test their hooks with server rendering which does not support suspense at all. I guess what I'm trying to say is that comments like these leave me very disheartened with the amount of care and effort I put into supporting all of our users the best way we can. I'm genuinely sorry that the break happened and has caused you the headaches it has. I'm also genuinely appreciative of the effort you put into identifying what the issue was and when it broke. |
Note: I've also updated the release notes for version 3.6. I was still learning with I'll close this now as the issue has been resolved but please feel free to continue the discussion. |
Thanks for the quick response, and explanation! I can see how Library usage of suspense and other hooksI can definitely see - at least in the current world - other hooks cases having utility without suspense. And obviously there's really no objective way to measure 'total impact' worldwide easily or accurately. I think for me having done both cases, the unique thing about Suspense is how incredibly convoluted testing was before this library. Vs other hooks in components, while not ideal, were not this level of difficulty. So from my experience it was the value of the use case - given that you had it - rather than the likelihood of a given person having a use case. Of course this is all sort of silly if both can be supported well. I was kind of surprised this didn't have good test coverage as an early adopter (#27) of the library for suspense use case I was under the impression at that time that it was a strong use case. My concern for depending on the library@rest-hooks/test uses What I realized yesterday is that since December 7th, anyone installing my library for the first time would experience a completely broken library. npm or yarn would resolve the ^3.2.1 to 3.6.0, which is broken and they would experience those errors. My test suite currently only uses the library to run its tests, so locking with yarn means I would not notice when minor/patch releases absolutely break since it will stay on 3.2.1. This is me relying on semver in an external package. Now I have a few choices. I can release a fix version where I get rid of A better world would be where 3.6.1 was released with this fix, so the entire 3 line wasn't destroyed. If this doesn't happen, I'm stuck in a situation where my trust in this library has let down the trust of my users. For the short term I can absolutely fix the version, which isn't ideal because any improvements won't be had, but at least I won't fear them breaking. So that's the context of that comment - the thinking I have to do to not let down my users. My intention was not for you to read it, as it certainly sounds harsh. I do appreciate the efforts you have put into this library, which is why I originally came to mark it as a dependency. I expect most people are simply adding this as a devdependency, and so these situations are no more than a minor annoyance. But if you can release a fix to the 3 line, I would very much appreciate it. Thanks again for the fix to the 5 line. |
(Although any fix to 3 line would also need |
Yes, I can see the issue you face. For what it's worth, it only would affect people asserting the pre-resolved values which in my experience, most people don't do (they only assert after the wait resolves), and I'm assuming you haven't seen any issues about it in the past month and a bit, so perhaps the issue isn't as widespread in actual usage as you're implying. I don't discount the risk of potential issues though. If it helps, fixing the version to That said, if it's still an issue for you, I'm happy to patch the older version for you to retain the I think you might be wrestling with what it actually means to have a dependency that is exposed to your users as well. I don't know exactly how we are exposed through your library (I haven't looked through your docs/code), but I cannot guarantee we will never ship another bug. I wish I could, but I can't. There will always be an inherent risks when you don't own the code yourself and the best you can hope for is that the dependencies you do have are actively maintained and are quick to push out patches when bugs are identified. If that risk is more than you're willing to accept, then perhaps forking and maintaining it yourself is the more appropriate action to take. You can always synchronise with the mainline when new versions are pushed out. Just be sure to PR any of your own fixes and features hack again for consideration 😉 |
Any idea why reactive/data-client#497 is failing now? This only happens in particular cases when i'm looping over providers (and obviously since upgrade to 5) |
I'm not sure off-hand. I'll take a look tonight for you. |
@ntucker it feels like you've got some leaky tests that are leaving promises that end up resolving after the test has finished. I can't quite put my finger on where the leaks are coming from, if from the source code or particular tests, but in many cases if you run the failing tests in isolation they pass and it's only when the whole file is is run that they issues appear. This appears the be the case for I have some theories why these leaks may have been hidden before:
Ok, I've done some digging with with local builds of older versions (I hacked them to ensure
So it appears to be a combination of removing
I'm not sure where this leaves us. Do either of those changes make sense to you as to why these tests would now be failing? The error boundary is a case where we plugged a hole in our existing The I'm hesitant to undo either of these changes until we understand better the precise cause of the issue. Your usage of this library would be close to the most complex I've seen to date and I'm not sure whether the root cause lies in our changes, your test code, or your production code (I hope it's not this one). |
Hmm the failing tests go from suspense to throwing an error. They never return a real value. The waitfornextupdate is waiting for a thrown error rather than return value. Perhaps catching on error boundary doesn't resolve waitfornextupdate? |
I think I have an idea for Perhaps there needs to be a mechanism to force reset the error boundary? |
Since the other issue is with another component that goes from suspending to throwing errors, perhaps this is also related to the error boundary change |
We do force the error boundary to reset when Is calling I have also contemplated whether we should have options for |
Is there some cleanup that might fail if the component is stuck in errorboundary? |
Not that I'm aware of. We unmount the test harness in an |
Actually, I've got an idea, but no time to investigate. I'll try to look later today. |
So my idea turned out to be nothing. The error boundary and the test harness are behaving as expected, so no leads there. I've discovered a few thing though:
All in all, I'm completely thrown by this issue. I've got no idea why that cleanup for that tests is literally killing the the test worker. I don't think it's related to any of the version 5 changes, however, I have not run the same debugging in an older version to rule it out (and my laptop battery is dangerously low again so I'll have to pick it up tomorrow now). All I can think is that by using |
Lol, I didn't notice my pun until just now... Unfortunately it's still true, I've got no idea why the test worker is just dying on All I do know is that our library is behaving as I expect, so I'm not sure where we go from here. |
Thanks so much for the investigation - this looks to be super helpful. Unfortunately I have to wrap up some other stuff for now so I have to put a pause on this til probably end of the week. I'll update on whatever I find there. Thanks again! |
No worries. I'll leave this open for now until were certain it's not an issue on our end. |
Actually, on second thought, I'll close this as the original issue and the one in the title has been resolved. Feel free to comment your findings here, and if there is another issue, we'll raise a new ticket for it. |
Hmm, errors existed in this lib before you added the error boundary - how did it work before? |
rerender() is what i can use to reset error boundary? |
It' was just a try/catch block on the inner render function (where the hook got called). We needed to change it because errors in useEffect were getting caught because they get called after rendering, but still synchronously with the component lifecycle |
Hmm this could be problematic.... for more complex test cases, the Provider (sent in wrapper) is expected to stay around as things evolve between renders. Users are expected to have error boundaries before the provider in actual applications...so catching errors above the wrapper (where I set the provider) doesn't really allow testing the normal flow. The previous behavior (while it broke effects - I wasn't testing those), did catch them before the wrapper. What about moving the error boundary below wrapper? Is there any problem with that? |
If other people expect wrapper to be below....could there be two options for above and below? (also I would expect this to be around suspense as well) |
Im not sure there's a rule about where exactly providers and error boundaries should belong in a real application, as it would depend on many factors, but I do appreciate that top level providers are very common. I don't think it would semantically change anything for us to move the error boundary within the wrapper, except that it won't catch provider errors (which the old version would fail to do as well). Are you able to change the order in node_modules locally and see if it helps? If you're unsure I can paste some code in here and line numbers to replace. |
Sorry, you expect the provider or the error boundary to be to be above suspense as well? Fundamentally, I'm open to moving the wrapper to the top level as we are about testing the hooks, not providers (despite their common coupling) and users can add their own suspense and error boundaries to the |
I'm pretty sure I'm on the right track for the last one. It has to do with ExternalCacheProvider - I have two providers - one that hooks completely into react state, and the other that uses redux (an external store). I'm currently looping over the same tests to do them on both providers. Doing just one or the other makes it all pass. I would expect Wrapper -> Suspense -> error boundary -> hook. Though I think I assumed react would not set everything back to its existing state after you un-fallback (I assumed it would run all mounting operations again?). Maybe this is wrong. |
I would have assumed this too. Can we test this? |
I've narrowed it down to this: it('should error on invalid params', async () => {
const { result, waitForNextUpdate, rerender } = renderRestHook(() => {
return useFetcher(TypedArticleResource.update());
});
try {
console.log('before');
// @ts-expect-error
await expect(result.current({ id: 'hi' }, { title: 'hi' })).rejects;
console.log('first reject');
await waitForNextUpdate();
console.log('update finished');
} catch (e) {
console.error('oh no', e);
}
rerender();
}); This is the last test in the file. I realized it was this after noticing the first or second test of the second run would always fail. Once I added the PS) All my comments... only 'before' and 'first reject' are output. 'update finished' and 'oh no' never display. |
Maybe why there's no stack trace is because all the handlers (in the provider) were removed by time the rejection occured - and with no handlers javascript just pops up the error as its own thing. |
Oh wait, this shouldn't actually throw in the hook since the hook just returns the dispatch function |
Hmm, it's unmounting the wrapper/provider before |
Could that be the error boundary unmounting it? |
No components should throw errors, but I need to confirm |
result.error is undefined - so error boundary would not be hit, right? |
Correct... unless |
Or it did rerender and now there is a result that cleared the error. You could log |
Only one result - the function as expected |
If the error came from the wrapper it would also show up - right? |
At the moment, yes, assuming you meant thrown during rendering of the wrapper. If it's putting a function into context that the hook is returning, and the error is thrown when that function is called, then no. We were proposing to put the wrapper outside the error boundary so that might be changing at some point. |
Hmm, just realized the unmount is actually from the last test. Do they not get unmounted after every test?
|
Yes they do. As part of the initial render we register a cleanup to unmount it that gets called in an |
I was able to verify the sequencing by outputting the state of the provider when it was unmounting. So there must be something going wrong because it unmounts after the next test run |
Our cleanup is I'll run some experiments when my kids go to bed. |
I.... I think I found it 😱 Try changing these lines like so: - // @ts-expect-error
- await expect(result.current({ id: 'hi' }, { title: 'hi' })).rejects;
- console.log('post reject');
- await waitForNextUpdate();
- console.log('post update');
+ await expect(
+ // @ts-expect-error
+ result.current({ id: 'hi' }, { title: 'hi' }),
+ ).rejects.toEqual(expect.any(Error));
+ console.log('post reject'); Not sure if you can make the matcher more appropriate for the test than just |
What does this mean? It doesn't actually wait forever if you don't compare? or is there some special path of cleanup? This worked for me too |
I think it's just that There is a bit of speculation here as I'm not confident what the result of not |
Oh wow, I didn't realize it wasn't actually a promise. I kinda expected typescript to complain about awaiting something that isn't a promise. Well, thanks for all your help - upgrade complete! :) |
Yeah, it because JS supports |
react-hooks-testing-library
version: 5.0.2react
version: 17.0.1react-dom
version (if applicable): 17.0.1react-test-renderer
version (if applicable):17.0.1node
version:14.13.1npm
(oryarn
) version:1.22.5Relevant code or config:
What you did:
Directly after the renderHooks() line with a function that throws a Promise
What happened:
Reproduction:
result.error just after a hook that suspends (don't await the resolution of suspense).
result.current was fixed in dc21e59 but result.error was not.
Problem description:
Suspense is the most important use of this library
Suggested solution:
Do the thing in dc21e59 but apply to all members
The text was updated successfully, but these errors were encountered: