react-jsonschema-form
react-jsonschema-form copied to clipboard
Editing widget type breaks the app
Editing widget type breaks the app
I cloned the playground app and it works fine. However when i use the same with the latest react version, then the app breaks while you are editing the schema widget type.
Steps to Reproduce
-
Clone the playground app..
-
Select any category like say simple and modify a widget from the UI schema like say age: It is "ui:widget": "updown", I delete updown and before I can type anything, the app breaks with the error:
Uncaught Error: No widget "" for type "integer"
Expected behavior
The json should show invalid schema and not break the app.
Actual behavior
The app breaks with the error: Uncaught Error: No widget "" for type "integer"
Version
React version 16.3 or higher. React-json-schema-form: 1.0.4
(Modifying just these did not break the app. Still mentioning just in case) codemirror: ^5.39.2 react-codemirror2: ^5.1.0
I put together a SSCCE with these versions:
It seems to work fine: https://codesandbox.io/s/93kmwl7n7p
I believe this is probably a problem in your environment. If there is another issue, feel free to bring it up.
Otherwise I suggest closing this issue.
Thanks for your investigation, @loganvolkers ! I'm closing presumptively. Feel free to re-open if you can reproduce, @ForumT !
Can we reopen this? I'm having this issue in one of my projects.
I don't know what the codesandbox was trying to reproduce, as the original issue was with ui:widget
. I've modified the codesandbox here:
https://codesandbox.io/s/charming-cannon-d29tk
Is this not supposed to be handled gracefully by onError
?
EDIT
~~Looks like there is a workaround for future readers:~~ https://github.com/rjsf-team/react-jsonschema-form/issues/612
This workaround is not working for me:
Sure. @TomKrcmar what behavior do you think should happen instead when a widget is invalid?
@epicfaace I may not be familiar enough with React internals, but I did some debugging, it looks like StringField
throws inside its render function due to utils.getWidget
- The render function can be called synchronously by a change in state to some higher component, in a call stack that skips my error boundary or even a manual call to ReactDOM.render('Form', ...)
inside a try/catch.
Imagine components A -> Boundary -> Form -> StringField
- When I call A.setState
, StringField throws an error inside its call stack, which does not include Form or Boundary. From what I can tell, It skips my error handling because React queued it previously and rendered it in a beginWork loop triggered by the higher component A. It also doesn't come back in Form.onError
. I'm finding it nearly impossible to catch the error without registering a global error handler in index.html that calls e.stopImmediatePropagation
and e.preventDefault
.
I think this may be related: https://stackoverflow.com/questions/52096804/react-still-showing-errors-after-catching-with-errorboundary https://stackoverflow.com/questions/46589819/disable-error-overlay-in-development-mode/47398520#47398520
React in development mode adds global error handlers that seem to ignore catching & handling, which may explain why the live playground demos don't throw?
--
But to answer your question - Is there some way you're able to catch the error that I'm not aware of? I'm not sure if onError
is a suitable place for invalid widget errors, but maybe there's a way to handle this more gracefully?
@TomKrcmar a more graceful way to handle it would be to do a similar thing to what we do when a schema has an unrecognized type -- see this playground example:
Might you be able to make a pull request that fixes this?
@epicfaace The playground crashes upon typing {"title": {}}
into the JSONSchema field.
Not an ideal solution, but the error can be caught with an error boundary.
For those facing an issue, this is what I ended up doing -
create an error boundary like so -
import { Alert } from 'antd';
import * as React from 'react';
interface ErrorProps {
data?: any;
}
interface ErrorState {
error: Error | null;
data: any;
}
export class ErrorBoundary extends React.Component<ErrorProps, ErrorState> {
constructor(props) {
super(props);
this.state = { error: null, data: null };
}
static getDerivedStateFromError(error: Error) {
// Update state so the next render will show the fallback UI.
return { error: error };
}
static getDerivedStateFromProps(props: ErrorProps, state: ErrorState) {
// We use this data field to ensure that the error state is reset whenever the input data changes.
// This allows us to have "live updates" to the underlying child components instead of always being
// stuck in the error state.
if (props?.data !== state?.data) {
return { error: null, errorInfo: null, data: props?.data };
}
return null;
}
render() {
if (this.state.error) {
return (
<Alert
message={`${this.state.error && this.state.error.toString()}`}
type='error'
/>
);
}
return this.props.children;
}
}
You can then use the error boundary like so -
// using ui schema and schema as the data field so that the error boundary is "reset" every time these two are changed
<ErrorBoundary data={JSON.stringify({ uiSchema, schema })}><JSONSchemaForm .../></ErrorBoundary>