There is an App which renders rjsf Form. When you call App's setState(updates value unrelated with the form's props) in Form.props.onChange then Form loses its internal state.
Form shouldn't loose its state.
Form lost its state.
1.0.4
Here is the new test which should fail:
import React from "react";
import { render, fireEvent } from "react-testing-library";
import Form from "../src";
it("should call provided change handler and keep the form state", () => {
const schema = { title: "Foo", type: "string" };
class App extends React.Component {
state = { calls: 0 };
handleChange = ({ formData }) => {
this.setState(prevState => ({ calls: prevState.calls + 1 }));
};
render() {
return (
<Form
schema={schema}
onChange={this.handleChange}
safeRenderCompletion={true}
/>
);
}
}
const { getByLabelText } = render(<App />);
const input = getByLabelText("Foo");
fireEvent.change(input, { target: { value: "bar" } });
expect(input.value).toBe("bar"); // => fails, received: ""
});
It doesn't fail in CodeSandbox (bug?) but if you download the demo and try it on the local then you see that it fails.
I've found the source of the issue. It's in the componentWillReceiveProps in the Form component.
Everytime when you update a parent component then Form will reevaluate the internal state. You should check props used in the getStateFromProps and __only__ after changes in these props you should call getStateFromProps.
I'm working with my fork (which uses templates from #1013 and it uses jest) but here is related commit.
Now I'm not using cWRP (I've completely removed it) at all. It looks like that the prop key is the better way for uncontrolled components.
Uh, it took me several hours to find this.
My current workaround: extend Form class and override componentWillReceiveProps to call the parent method only if it is really required.
Any update on this? @Leksat can you please guide how can I follow your work around using hooks?
@epicfaace any work around would be very appreciated. Thanks
@affan-speridian
import Form from 'react-jsonschema-form';
class FixedForm extends Form {
componentWillReceiveProps(nextProps) {
const shouldSkipUpdate =
Object.keys(nextProps).length === Object.keys(this.props).length &&
Object.keys(nextProps).filter(
key =>
key !== 'children' &&
// Maybe some other logic.
nextProps[key] !== this.props[key]
).length === 0;
if (shouldSkipUpdate) {
return;
}
super.componentWillReceiveProps(nextProps);
}
}
@Leksat hey, thanks for responding. Much appreciated. I just tested it. It works but for a few seconds.
I mean it preserves the internal state but as I go along filling the form. After few seconds older one got refreshed..
Here are my code:
form.js
import React from 'react'
import Form from 'react-jsonschema-form';
class FixedForm extends Form {
constructor(props){
super(props)
}
componentWillReceiveProps(nextProps) {
const shouldSkipUpdate =
Object.keys(nextProps).length === Object.keys(this.props).length &&
Object.keys(nextProps).filter(
key =>
nextProps[key] !== this.props[key]
).length === 0;
if (shouldSkipUpdate) {
return;
}
super.componentWillReceiveProps(nextProps);
}
render() {
return <Form {...this.props}/>
}
}
export default FixedForm
my custom-form.js in components folder
import React from 'react'
import RForm from './form'
export const Form = (props) => {
return (
<RForm {...props} widgets={{
BaseInput: TextInput,
CheckboxWidget,
FileWidget: FileUpload,
SelectWidget
}}>
<Spacer />
<Button submit type="primary">{props.submitText}</Button>
<Loader loading={props.loading} />
</RForm>
)
}
then in my component where I use it, I do
import { Form } from '../../custom-form'
<Form schema={schema} onSubmit={onSubmit} onChange={onChange} submitText="Add" noValidate={true} loading={loading} />
Hi, I am having this issue too, is there any update on this bug already ? In the meantime I'll use this workarround :)
Most helpful comment
I've found the source of the issue. It's in the
componentWillReceivePropsin theFormcomponent.Everytime when you update a parent component then Form will reevaluate the internal state. You should check props used in the
getStateFromPropsand __only__ after changes in these props you should callgetStateFromProps.I'm working with my fork (which uses templates from #1013 and it uses jest) but here is related commit.