Error Handling#
React errors can crash your entire UI. Use ErrorBoundary to catch errors and display fallback UI instead.
Import#
import { ErrorBoundary } from "onejs-react"Basic Usage#
Wrap components that might throw errors:
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>If MyComponent throws an error, ErrorBoundary displays a default error UI instead of crashing.
Custom Fallback#
Provide your own fallback UI with fallbackRender. It receives the error and a reset function that renders the children again:
<ErrorBoundary fallbackRender={({ error, reset }) => (
<View>
<Label text={`Something went wrong: ${error.message}`} />
<Button text="Try Again" onClick={reset} />
</View>
)}>
<MyComponent />
</ErrorBoundary>Fallback with Error Details#
errorInfo carries React's component stack. It is filled in on the render after the error, so it is null the first time the fallback runs:
<ErrorBoundary
fallbackRender={({ error, errorInfo }) => (
<View style={{ padding: 20, backgroundColor: "#2d1b1b" }}>
<Label
text={`Error: ${error.message}`}
style={{ color: "#ff6666", fontSize: 16 }}
/>
<Label
text={errorInfo?.componentStack ?? ""}
style={{ color: "#ffaaaa", fontSize: 12, marginTop: 10 }}
/>
</View>
)}
>
<MyComponent />
</ErrorBoundary>Props#
| Prop | Type | Description |
|---|---|---|
children | ReactNode | Components to wrap |
fallbackRender | ({ error, errorInfo, reset }) => ReactNode | Fallback UI |
resetKeys | unknown[] | Reset when any entry changes |
onError | (error, errorInfo) => void | Called once per caught error |
onReset | (details) => void | Called after a reset; details.reason is "imperative-api" or "keys" |
The older fallback prop, a node or an (error, errorInfo) function, still works but is deprecated.
Error Logging#
The runtime already logs every caught error to the Console once, with its component stack. Use onError to report it somewhere else:
<ErrorBoundary
onError={(error, errorInfo) => {
// Send to error tracking service
trackError(error, errorInfo.componentStack)
}}
>
<App />
</ErrorBoundary>Resetting the Boundary#
Calling reset from the fallback renders the children again. onReset runs before they do, which is where to clean up whatever caused the error:
<ErrorBoundary
fallbackRender={({ reset }) => (
<View>
<Label text="Error occurred" />
<Button text="Try Again" onClick={reset} />
</View>
)}
onReset={() => resetAppState()}
>
<MainContent />
</ErrorBoundary>A boundary can also reset itself when what the failure depended on changes. Pass those values as resetKeys; when any of them changes while the fallback shows, the boundary tries the children again:
<ErrorBoundary resetKeys={[levelId]} fallbackRender={() => <Label text="This level failed to load" />}>
<Level id={levelId} />
</ErrorBoundary>Nested Boundaries#
Use multiple boundaries for different sections:
function App() {
return (
<View>
<ErrorBoundary fallbackRender={() => <Label text="Header error" />}>
<Header />
</ErrorBoundary>
<ErrorBoundary fallbackRender={() => <Label text="Content error" />}>
<MainContent />
</ErrorBoundary>
<ErrorBoundary fallbackRender={() => <Label text="Footer error" />}>
<Footer />
</ErrorBoundary>
</View>
)
}This way, an error in the header doesn't affect the rest of the app.
Format Error Utility#
The formatError helper creates readable error strings:
import { ErrorBoundary, formatError } from "onejs-react"
<ErrorBoundary
fallbackRender={({ error, errorInfo }) => {
const formatted = formatError(error, errorInfo?.componentStack ?? undefined)
console.log(formatted)
return <Label text="Error logged" />
}}
>
<MyComponent />
</ErrorBoundary>Output format:
Error: Something went wrong
Stack:
at MyComponent
at View
at App
Component:
at MyComponent
at ErrorBoundary
at AppWhat Errors Are Caught#
ErrorBoundary catches:
- Errors in render methods
- Errors in lifecycle methods
- Errors in constructors
ErrorBoundary does NOT catch:
- Errors in event handlers (use try/catch)
- Async errors (use try/catch in async functions)
- Errors thrown outside React (global errors)
A promise that rejects with nothing to handle it is logged to the Console as an error, [OneJS] Unhandled promise rejection: followed by the reason and its stack, so a failed await with no catch is never silent. On WebGL the browser reports it in its own console instead.
Event Handler Errors#
Handle errors in event handlers manually:
function SafeButton() {
const handleClick = () => {
try {
doSomethingRisky()
} catch (error) {
console.error("Click error:", error)
// Show error to user
}
}
return <Button text="Click" onClick={handleClick} />
}Async Error Handling#
function AsyncComponent() {
const [error, setError] = useState<string | null>(null)
useEffect(() => {
async function loadData() {
try {
const data = await fetchData()
// Use data
} catch (e) {
setError(e instanceof Error ? e.message : String(e))
}
}
loadData()
}, [])
if (error) {
return <Label text={`Error: ${error}`} style={{ color: "#ff6666" }} />
}
return <Label text="Content" />
}Full App Example#
import { render, ErrorBoundary, View, Label, Button } from "onejs-react"
function App() {
return (
<ErrorBoundary
fallbackRender={({ error, reset }) => (
<View style={{ padding: 40, alignItems: "center" }}>
<Label
text="Oops! Something went wrong."
style={{ fontSize: 20, color: "#ff6666", marginBottom: 20 }}
/>
<Label
text={error.message}
style={{ color: "#ffaaaa", marginBottom: 20 }}
/>
<Button text="Try Again" onClick={reset} />
</View>
)}
onError={(error, info) => {
// The runtime has already logged it; send it to a service here
// analytics.trackError(error, info.componentStack)
}}
>
<MainApp />
</ErrorBoundary>
)
}
render(<App />, __root)