Why do React components that throw errors render twice? - reactjs

I've been experimenting with components that do a react-cache style thing and do web service calls right in the render method, throwing a promise up to a React.Suspense component and re-rendering when the data is there. They call a web service, check the response, and either render or throw an error up to an error boundary depending on the response. I've noticed that whenever an error is thrown in a component, it renders twice. The first time the callstack looks normal, and the second time the callstack includes calls to invokeGuardedCallbackDev and invokeGuardedCallback, which seem to have something to do with React ensuring that errors appear in the console even when "caught" by an error boundary in a dev build.
I can reproduce this with react and react-dom 16.8.6 by just rendering a component like this: https://codesandbox.io/s/components-that-throw-render-twice-i26qc.
I'm wondering why this happens, because it's causing the components to re-fetch data from the web service, re-throw another promise, and results in an "Uncaught Promise" error appearing in the console.

This seems to be caused by a recent change in react/react-dom. If you revert both to version 16.0.0, you will see that it only renders the component once. See: https://codesandbox.io/s/components-that-throw-render-twice-03fdb
Looking at the version history, there seem to be a couple of bugs fixed relating to error handling in React, so it seems like this re-rendering is a result of a workaround for one of those bugs.
However, this should not be a problem for your app, as the render function should be pure (no side effects) in React apps. So basically, React can call your render function anytime it wants/needs to.
To work around this, you should avoid relying on the component not re-rendering and instead use an effect hook or similar to only fetch when certain props/state change.
Source: https://github.com/facebook/react/issues/16130#issuecomment-521637592

I think the error boundary is not actually catching the thrown error.
From https://reactjs.org/docs/error-boundaries.html:
Note
Error boundaries do not catch errors for:
Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
Asynchronous code includes Promises in this case.
See also https://reactjs.org/docs/error-boundaries.html#how-about-event-handlers:
Error boundaries do not catch errors inside event handlers.
If you need to catch an error inside event handler, use the regular JavaScript try / catch statement.

Related

Is there a way to prevent a broken react render from crashing the whole page?

If a breaking error occurs in a React component, the whole page crashes. For instance, if x is null and you try to access x.someProperty, you'll get an error and the whole page will go white. This is a little bit different from an old-style app that isn't running entirely on JS, because the markup (HTML&CSS) would still be there even if the JS errored out and blocked some aspects of the page.
Is there a way to mitigate this with React? Something like wrapping a component in a try/catch so that if something goes wrong, only that component fails and only that part of the page goes white, rather than the entire page. I'm not sure if there's a better pattern than literally wrapping the entire body of a functional component's code in a try/catch.
I suppose I'm particularly interested in functional components here, but a class-based answer might be useful for someone else.
You can mitigate such errors using Error Boundaries - such components may catch errors thrown from the child components and display some meaningful error instead of just crashing.

Can not trace react-hook callstack in Chrome

I try to trace an error in Chrome XHR network panel
When I hover over VM1716:1
I saw something like this
I can see the XHR is triggered by #useUser.js:13. It is already helpful, but it is even more helpful to know who trigger #useUser.js:13. Clearly, some react component should trigger the hook, but I fail to track the component
Why that happens how can I know which component triggers the hook
Same thing in console panel, no hint where the hook is invoked

Using testing-library findBy* on a component that uses setState causes a "not wrapped in act" warning

I've got a React component which retrieves data using a Promise and then calls setState when the promise resolves. It then uses the updated state to change what's rendered.
When testing this, I use the testing library's findBy* methods to wait for the rendered component to have been updated with the content from the Promise:
await findByText("Failure details", { exact: false });
My tests pass - they correctly wait until the component has re-rendered. However, I get a warning like this when running them (I'm using Jest, but I don't think that's relevant):
Warning: An update to MyComponent inside a test was not wrapped in act(...).
Everything I've found online that's related to this is either about components using useEffect or about tests which include explicit calls to act in them (mine don't), which I don't think should be necessary here - my test itself isn't changing the component, apart from the fact that it calls render.
In short, my situation seems simpler than those that others are writing about - I'm just using setState and the findBy* queries, nothing more complex.
Is there an equally simple solution to this problem?
Thankfully, the solution was simple - upgrading React and React-DOM to >= 16.9.0. I didn't have to change my tests or my component.
16.9.0 includes an asynchronous version of act. As I say, I didn't have to explicitly use it, but it was obviously being used behind the scenes because it solved my problem.

Error in React component causing app to re-render, which is causing infinite loop. Why?

Without including code yet, I am wondering if anyone has ran into an issue where you:
Have a component which is wrapped in connect and, in my case apollo-client
Have child components also wrapped in connect that do a dispatch in componentWillMount
Throw an error in a child component
Get an infinite loop
The situation I am in is that any child that throws an error causes the parent component to run render again and all children seem to run componentWillMount, but do not run componentWillUnmount.
The error is not logged and does not appear in the console until the call stack overflows.
This causes them to all re-connect to Redux, dispatch the action as well as throw the error again because they are mounting again, which causes the loop.
I can try to repro, but that will take quite a bit of time, just wondering if someone has run into a similar issue that could offer somewhere to look.
You should not be dispatching any actions in componentWillMount. Anything that is considered mutable should be done in componentDidMount. componentWillMount is similar to a constructor. The component hasn't mounted. The component is unaware of any state (unless you specify) but that state is not fulfilled until the component actually mounts, which happens in componentDidMount. Ideally, you should stick to the constructor and not really use componentWillMount. For any API calls or dispatching, you should keep those in cDM
Clayton is right; ComponentWillMount is somewhat of an anti-pattern, despite having once been the way and the truth. You probably want to use ComponentDidMount.
However, since you mentioned throwing, it's worth nothing that React 16 has a componentDidCatch lifecycle hook. This allows you to catch the error and handle it in react:
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI
In theory, this should allow you to handle the error and short-circuit connect.

How to handle all unhandled errors in both sync and async operations in React?

I've spent a bit of time recently trying to find a comprehensive and reliable way of handling errors in React for both sync and async operations. I'm using React 15.5.4 and TypeScript 2.4.1.
My goal is to catch errors in sync and async operations(lifecycle methods, event handlers) and display them in a ErrorDisplay component that is the closest parent of the component that triggered the error.
This is what I have tried but none of the solutions seem to cover all edge cases:
unstable_handleError as per https://github.com/facebook/react/issues/2461#issuecomment-311077975 handles only errors in render, does not handle errors in event handlers
custom batch updater as per https://engineering.classdojo.com/blog/2016/12/10/catching-react-errors/, works in production only, doesn't seem to work with event handlers
There is one more approach that I'm planning to try which is to wrap each method at runtime and use React context to pass information re where a given error needs to be displayed. I hesitate a bit to use this approach because React context API is still marked as experimental.
Thoughts?

Resources