but UI errors can be solved at development time
UI errors can't be solved at development time, although typing like Flow & Typescript indeed help a lot, still, there may be run-time errors that usually come for the data server.
Error Boundaries used for catching run time errors and displaying a friendly UI.
See Compile time vs Run time errors.
ErrorBoundary is a class components that implements getDerivedStateFromError and componentDidCatch in order to add additional render on fall back UI.
Currently, it can only be implemented with a class component, typically its the only class component in your application, the rest is function components with hooks.
In real-life use cases, you wrap your application with some <ErrorBoundary/> component and render the desired UI.
class ErrorBoundary extends React.Component {
  state = {
    hasError: false,
    error: { message: '', stack: '' },
    info: { componentStack: '' }
  };
  static getDerivedStateFromError = error => {
    return { hasError: true };
  };
  componentDidCatch = (error, info) => {
    this.setState({ error, info });
  };
  render() {
    const { hasError, error, info } = this.state;
    const { children } = this.props;
    return hasError ? <ErrorComponent/> : children;
  }
}
<ErrorBoundary>
  <App/>
</ErrorBoundary>