A deep dive on forms with modern React
React 19 introduces terrific primitives for building great forms. Let's dive deep on forms for the web with React.

"Why can't I just use try/catch in my React components?" If you've ever asked this, you're not alone! Let's dig into why React error boundaries exist, how they work, and why they're not just a fancy try/catch block for your UI.
No matter how careful you are, things will go wrong in your app. Maybe a network
request fails, a typo sneaks in, or a third-party library throws a fit. In plain
JavaScript, you might reach for try/catch
:
try { // Something that could error doSomethingDangerous()} catch (error) { // Handle the error showErrorToUser(error)}
But in React, things are a bit different. When you write JSX like this:
const element = ( <div> <h1>Calculator</h1> <Calculator left={1} operator="+" right={2} /> <Calculator left={1} operator="-" right={2} /> </div>)
React doesn't actually call your Calculator
function when it creates this
element. It just creates a description of what should be rendered. The real work
happens later, when React walks the tree and calls your components. That's why
you can't just do this:
try { const element = ( <div> <h1>Calculator</h1> <Calculator left={1} operator="+" right={2} /> <Calculator left={1} operator="-" right={2} /> </div> )} catch (error) { // This won't catch anything!}
If you try to wrap your JSX in a try/catch
, you'll only catch errors that
happen during the creation of those elements—not when React actually renders
them. The real errors happen inside your components, during rendering,
effects, or event handlers.
You could do this:
function Calculator(props) { try { // ...render logic } catch (error) { return <div>Oh no! An error occurred!</div> }}
But that's a nightmare to maintain. Imagine adding this boilerplate to every single component! Plus, it doesn't help with errors that happen deeper in the tree, or in lifecycle methods, or async code.
React introduced error boundaries to solve this. An error boundary is a special kind of component that catches errors anywhere in its child component tree, logs those errors, and displays a fallback UI instead of crashing the whole app.
Here's a minimal error boundary:
class ErrorBoundary extends React.Component { state = { error: null } static getDerivedStateFromError(error) { return { error } } render() { if (this.state.error) { return this.props.fallback } return this.props.children }}
And you use it like this:
<ErrorBoundary fallback={<div>Something went wrong!</div>}> <MyComponent /></ErrorBoundary>
Now, if MyComponent
or anything it renders throws during rendering, React will
show your fallback UI instead of a blank screen.
Right now, only class components can be true error boundaries because they use
the special static method getDerivedStateFromError
. There's no direct
equivalent in function components (yet!). That's why libraries like
react-error-boundary
exist—they give you a nice API and let you use hooks to interact with error
boundaries.
Error boundaries only catch errors during rendering, (legacy) lifecycle methods, and constructors. They don't catch errors in event handlers, async code, or effects. For those, you need to surface the error to React yourself.
Here's how you might do it with
react-error-boundary
:
import { useErrorBoundary } from 'react-error-boundary'function MyComponent() { const { showBoundary } = useErrorBoundary() async function handleClick() { try { await doSomethingAsync() } catch (error) { showBoundary(error) } } return <button onClick={handleClick}>Do something</button>}
You can nest error boundaries anywhere in your component tree, just like you'd nest try/catch blocks in code. This lets you show different fallback UIs for different parts of your app:
function App() { return ( <div> <ErrorBoundary fallback={<div>Something went wrong with the list.</div>}> <List /> </ErrorBoundary> <ErrorBoundary fallback={<div>Something went wrong with the details.</div>} > <Details /> </ErrorBoundary> </div> )}
Sometimes, errors are temporary. Maybe the user lost their internet connection,
or a form submission failed. With react-error-boundary
, your fallback
component can offer a "Try again" button:
function ErrorFallback({ error, resetErrorBoundary }) { return ( <div role="alert"> <p>Something went wrong:</p> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}>Try again</button> </div> )}const element = ( <ErrorBoundary FallbackComponent={ErrorFallback}> <MyComponent /> </ErrorBoundary>)
Error boundaries can also help you log errors for analytics or debugging. Use
the onError
prop with react-error-boundary
:
<ErrorBoundary FallbackComponent={ErrorFallback} onError={(error, info) => { // Send error to your logging service logErrorToService(error, info) }}> <MyComponent /></ErrorBoundary>
react-error-boundary
for
a modern, ergonomic API.Want to learn more? Check out the Epic React Fundamentals workshop and the react-error-boundary docs for deeper dives and more examples.
Delivered straight to your inbox.
React 19 introduces terrific primitives for building great forms. Let's dive deep on forms for the web with React.
React Server Components are going to improve the way we build web applications in a huge way... Once we nail the abstractions...
How React preserves or resets state depends on component position and keys. Learn why state disappears and how to fix it as well as links to more resources!
Why can't React just magically know what to do without a key?