Reputation: 407
I want to create a form to save some form of data model in a database.
Let's say I have a class:
export default class Model {
constructor(model = {}) {
this.x = model.x || '';
this.y = model.y || '';
}
save() {
// code to save 'this' to database
}
}
And a component:
import { React, useState } from 'react';
export const ModelForm = () => {
const [model, setModel] = useState(new Model());
const handleInputChange = (event) => {
const target = event.target;
setModel({ ...model, [target.name]: target.value });
};
const handleSubmit = (event) => {
event.preventDefault();
const modelObject = new Model(model);
modelObject.save();
};
return (
<form onSubmit={handleSubmit}>
<input type='text' name='x' value={model.x} onChange={handleInputChange} />
<input type='text' name='y' value={model.y} onChange={handleInputChange} />
<button type='submit'>Submit</button>
</form>
);
};
This thing is currently working, but I have a question.
Every time I do setModel, I have to destructure the model object and change the value accordingly. Since I destructure it, when I am submitting the form, the object in the component is no longer a Model object, but a simple javascript Object. So, as you can see, I have to create a new one to use save() on it.
Is my approach good or there is a better way to do it?
Also, is it a good practice to use ES6 classes in useState? Are there any downfalls?
Upvotes: 4
Views: 2917
Reputation: 94
In my personal opinion, I don't think using ES6 classes in the state is a good idea. In fact, in the react useState hook documentation say that if you need to have two states on your component you should put two useState hooks.
// If there are too many states, you can use useReducer hook
const [state1, setState1] = useState();
const [state2, setState2] = useState();
I prefer use the useState hooks with primitives values, because as React works, when you change the state, if the value of the new state is equal to the old one, the component doesn't re-render because react do that comparison by itself, but when you use a object or any other non primitive value, even if the props of the object and values are the same, the reference to that object is going to be different, so it will re-render when it should not. Remember that when JavaScript compares non primitive values it compares its references in memory so:
const obj1 = {prop: 'a'};
const obj2 = {prop: 'a'};
console.log(obj1 === obj2); // This logs false because they are different objects and they references in memory are not equal
console.log(obj1 === obj1); // This logs true, because its reference is the same.
console.log(obj1.prop === obj2.prop); // This logs true because they are strings (primitive values)
Upvotes: 1
Reputation: 664444
I have to create a [
new Model
] to usesave()
on it.
That's certainly a viable approach. You wouldn't actually store a Model
instance in your state, but only the options object for your constructor. Initialise it as simply
const [model, setModel] = useState({});
(and possibly rename it to modelArgs
or modelOptions
)
Since I destructure it, […] the object in the component [state] is no longer a
Model
object, but a simple javascript Object.
It's actually not destructuring, rather object literal spread syntax, but yes - you're creating a plain object in your setModel
call.
To fix the problem, you'd need to pass a model instance there - a new one, since React state is designed around immutability. You can easily achieve that though:
export default class Model {
constructor(model = {}) {
this.x = model.x || '';
this.y = model.y || '';
}
withUpdate({name, value}) {
// if (!['x', 'y'].includes(name)) throw new RangeError(…)
return new Model({...this, [name]: value});
// or optimised (?):
const clone = new Model(this);
clone[name] = value;
return clone;
}
save() {
// code to save 'this' to database
}
}
const [model, setModel] = useState(new Model());
const handleInputChange = (event) => {
setModel(model.withUpdate(event.target));
};
Upvotes: 3
Reputation: 2627
I agree that it is very usefull to have es6 classes in your state sometimes. A decent solution is just to create a derived variable.
import { React, useState } from 'react';
export const ModelForm = () => {
const [_model, setModel] = useState({});
const model = new Model(_model)
const handleInputChange = (event) => {
const target = event.target;
setModel({ ...model, [target.name]: target.value });
};
const handleSubmit = (event) => {
event.preventDefault();
const modelObject = new Model(model);
modelObject.save();
};
return (
<form onSubmit={handleSubmit}>
<input type='text' name='x' value={model.x} onChange={handleInputChange} />
<input type='text' name='y' value={model.y} onChange={handleInputChange} />
<button type='submit'>Submit</button>
</form>
);
};
Upvotes: 1