Reputation: 3098
I recently refactored my React App to use Immer. However, in the onFormValueChange
using produce
gives me the error TypeError: illegal operation attempted on a revoked proxy
while the version that is written without produce
works fine.
This is the smallest I could reduce the relevant code to :
test.tsx
import { produce } from 'immer';
import { IFormValues, TestForm } from './test_form';
interface ITestProps {}
interface ITestState {
type: string;
}
export class Test extends React.Component<ITestProps, ITestState> {
constructor(props: ITestProps) {
super(props);
this.state = {
type: '',
};
}
handleSubmit = (values: IFormValues) => {
console.log.log(values);
};
onFormValueChange = (values: IFormValues) => {
this.setState(
produce((draft: ITestState) => {
draft.type = values.type;
}),
);
};
// The Following version of the function works perfectly fine and as expected:
//
// onFormValueChange = (values: IFormValues) => {
// this.setState({
// type: values.type,
// });
// };
render() {
let showField = true;
if (this.state.type === 'test') {
showField = false;
}
return (
<div>
<TestForm
submit={this.handleSubmit}
onValueChange={this.onFormValueChange}
>
<input name="type" />
</TestForm>
{showField && this.state.type}
</div>
);
}
}
test_form.tsx
import produce from 'immer';
export interface IFormValues {
[key: string]: any;
}
interface IFormProps {
submit: (values: IFormValues) => void;
onValueChange?: (values: IFormValues) => void;
}
export interface IFormState {
values: IFormValues;
}
interface IFieldProps {
value: any;
name: string;
onChange: (event: any) => void;
}
export class TestForm extends React.Component<IFormProps, IFormState> {
constructor(props: IFormProps) {
super(props);
const values: IFormValues = {};
this.state = {
values,
};
}
private handleSubmit = (event: any) => {
event.preventDefault();
const { submit } = this.props;
const { values } = this.state;
submit(values);
};
handleChange = (event: any) => {
const { name, value } = event.target;
this.setState(
produce((draft: IFormState) => {
draft.values[name] = value;
this.props.onValueChange && this.props.onValueChange(draft.values);
}),
);
};
public render() {
const { values } = this.state;
return (
<form onSubmit={this.handleSubmit} noValidate={true}>
<div>
{React.Children.map(
this.props.children,
(child: React.ReactElement<IFieldProps>) => (
<div>
{React.cloneElement(child, {
value: values[child.props.name],
onChange: this.handleChange,
})}
</div>
),
)}
<div>
<button type="submit">Submit</button>
</div>
</div>
</form>
);
}
}
Upvotes: 2
Views: 3670
Reputation: 1074335
Caveat: I've never used Immer. But the error is quite clear: You're trying to use a revoked Proxy. My guess is the fundamental problem is here:
this.setState(
produce((draft: IFormState) => {
draft.values[name] = value;
this.props.onValueChange && this.props.onValueChange(draft.values); // <=======
}),
);
In produce
, you're passing draft.values
into a function that will call produce
a second time and put values.type
on a different draft state. My guess is you're not allowed to pass data out of the original produce
call in that way. (The documentation says "Warning: please note that the draft shouldn't be 'leaked' from the async process and stored else where. The draft will still be revoked as soon as the async process completes." but that warning is in relation to async producers, and yours aren't async. Still, it may be that it's a more general warning, it just happens to be in the async producers part.)
If so, this change to handleChange
in TestForm
would fix it:
this.setState(
produce((draft: IFormState) => {
draft.values[name] = value;
}),
() => {
this.props.onValueChange && this.props.onValueChange(this.state.values);
}
);
That ensures that it calls onValueChange
with the value after the state has been set (presumably it's a normal object at that point, not a proxy).
Upvotes: 1