-
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
Unable to test hooks that Suspend #27
Comments
I'd say this was was an unintended side effect of #21, where the intention was to catch errors to assert against. I wonder if just rethrowing the caught value would be enough to get this behaviour back. We probably also don't want to be setting Could you try locating this section, but in the babel output version in the function TestHook({ callback, hookProps, children }) {
try {
children(callback(hookProps))
return null
} catch (e) {
if (!e.then) { // we'll probably find a better promise check, but this will do for testing purposes
children(undefined, e)
}
throw e
}
} and see if it starts working again. If it does, I'm happy for you to submit this as a PR. Otherwise, if you don't have time for this, I will take a closer look tonight (it's currently 8:30am my time) when the kids go to bed. |
Actually I was looking forward to being able to inspect the result.error so I could simply await it. Perhaps a better approach would be to set result.promise if a promise is thrown? Before I was setting Suspense and ErrorBoundary in my wrapper just to call a passed in function so I knew when they were hit. |
I wonder if we can somehow tie it into |
Changing the provided code caused the react warning to go away. However, my fbmock that's triggered on render of fallback from suspense wasn't being called. Edit: actually the error comes back when I remove the expect check for the fbmock call. |
Full trace:
Somehow definitely seems related to facebook/react#14821 since that's about useMemo() |
I'm having a little trouble setting up a simple test hook that properly suspends the React rendering. Basically I have a fake fetch promise that I want to wait for const fetchName = () =>
new Promise((resolve) => {
setTimeout(() => resolve('Bob'), 50)
}) How would you write a simple hook the suspends until this has resolved? So far I've got: const useFetchName = () => {
const [name, setName] = useState()
const fetcher = useMemo(fetchName, [])
useEffect(() => {
fetcher.then((theName) => {
setName(theName)
})
}, [])
if (!name) {
throw fetcher
}
return name
} but this ends up calling I don't really know what I'm doing with Suspense here, so it could be completely wrong, but my theory was to throw the same promise until it resolves and then return the name. Any help would be much appreciated. |
You need to throw the promise (the exact same one) until it resolves. Once it resolves you do whatever else. The easiest way is to just use a singleton like react-cache does. const cache = {}
function get(key) {
if (!(key in cache)) {
cache[key] = new Promise((resolve) => {
setTimeout(() => resolve('Bob'), 50)
}).then(value => cache[key] = value).catch(e => cache[key] = e);
}
return cache[key]
}
const SuspendMe = () => {
const myvalue = get('stuff');
if (typeof myvalue.then === 'function') {
throw myvalue;
} else if (myvalue instanceof Error) {
// you'll need an <ErrorBoundary /> above this component in the tree to handle this
throw myvalue;
}
return myvalue;
} |
I'm stil looking at this but struggling a bit when the promise rejects (seems to go into a infinite spin somewhere and I'm not sure why). |
oh ya, that only handles happy path. I'll update my comment to include rejection |
in the real world the error would probably be set somewhere else, but this is just the dead simple handler |
Thanks. In my head, if the thrown promise rejects, it would trigger the error boundary, but in practice, I see that this is not the case. Explicitly throwing the error does pass my test as expected. I did have a variation of this that would capture the error from the thrown promise, which was nicer in that you wouldn't have to work out some way of capturing the error to throw yourself in the hook, but I'm thinking now that we want the |
Yeah, the throw has to originate on the react render call stack. But async events have their own callstack. Adding the .catch() for the promise allows us to grab it from the async callback so we can then throw it in the render call stack. It actually makes a lot of sense when you think about it that way. |
Add Suspense and ErrorBoundary to better support suspending from hooks Fixes #27
react-hooks-testing-library
version: 0.4.0react-testing-library
version: 6.0.3react
version: 16.8.6node
version: 11.10.0npm
(oryarn
) version: 1.15.2Relevant code or config:
What you did:
Running unit tests on 'rest-hook's library. Trying to update from react-testing-library - which works fine - to react-hooks-testing-library.
(https://github.com/coinbase/rest-hooks/blob/master/src/react-integration/__tests__/integration.tsx)
What happened:
Invariant Violation: Rendered more hooks than during the previous render.
Reproduction:
https://github.com/coinbase/rest-hooks/blob/update-test/src/react-integration/__tests__/integration.tsx#L167
(run
yarn test
on that branch)Problem description:
Impossible to test hooks that use suspense. (This was fixed in React 16.8.2 facebook/react#14821)
Suggested solution:
The text was updated successfully, but these errors were encountered: