Should ES6 classes be used directly as React state?
I want to define an ES6 class that:
However, calling setState
does not appear to diff class members, at least as far as I can tell.
Using the following class:
class Document{
constructor(){
this.title = "";
this.body = "";
}
syncWithDatabase = async () => {
// do some logic to update the database
}
}
And this ponent:
// import Document from "...";
export default function Sandbox() {
const [document, setDocument] = useState(new Document());
const [renderTrigger, setRenderTrigger] = useState(false);
return (
<div>
<div>{document.title}</div>
<div>{document.body}</div>
<button
onClick={() => {
document.title = 'Some Default Title';
document.body = 'lorem text';
document.syncWithDatabase(); // being able to take this type of action in this way is why I'm trying to use classes.
setDocument(document);
}}
>
Set Canned Data
</button>
<div>Render trigger is: {renderTrigger ? 'true' : 'false'}</div>
<button onClick={() => setRenderTrigger(true)}>Force Render</button>
</div>
);
}
Clicking the first button will set the title and body on the instance of Document
held react state, but it will not update the UI.
Clicking the second button to force a re-render in a way that I am confident will work makes the updated members of document
render out, even though they didn't when setDocument
is called.
Creating a new object with new Document()
and passing it setDocument
WILL trigger a re-render. So I'm thinking that react isn't doing a deep pare or is seeing that the reference to the Document
object has not changed, and therefore not re-rending.
So, is it possible to change an object's members, pass that object to a setState hook and have it update the UI, without creating an entirely new object? Or should I avoid doing what I'm trying to do here?
Should ES6 classes be used directly as React state?
I want to define an ES6 class that:
However, calling setState
does not appear to diff class members, at least as far as I can tell.
Using the following class:
class Document{
constructor(){
this.title = "";
this.body = "";
}
syncWithDatabase = async () => {
// do some logic to update the database
}
}
And this ponent:
// import Document from "...";
export default function Sandbox() {
const [document, setDocument] = useState(new Document());
const [renderTrigger, setRenderTrigger] = useState(false);
return (
<div>
<div>{document.title}</div>
<div>{document.body}</div>
<button
onClick={() => {
document.title = 'Some Default Title';
document.body = 'lorem text';
document.syncWithDatabase(); // being able to take this type of action in this way is why I'm trying to use classes.
setDocument(document);
}}
>
Set Canned Data
</button>
<div>Render trigger is: {renderTrigger ? 'true' : 'false'}</div>
<button onClick={() => setRenderTrigger(true)}>Force Render</button>
</div>
);
}
Clicking the first button will set the title and body on the instance of Document
held react state, but it will not update the UI.
Clicking the second button to force a re-render in a way that I am confident will work makes the updated members of document
render out, even though they didn't when setDocument
is called.
Creating a new object with new Document()
and passing it setDocument
WILL trigger a re-render. So I'm thinking that react isn't doing a deep pare or is seeing that the reference to the Document
object has not changed, and therefore not re-rending.
So, is it possible to change an object's members, pass that object to a setState hook and have it update the UI, without creating an entirely new object? Or should I avoid doing what I'm trying to do here?
===
is going to identify your instances as the same thing each time.
– Jamie Dixon
Commented
Oct 27, 2019 at 17:46
You can (but probably shouldn't, see below) use an object created by a constructor function (which is what document
is in your code) as state. What you can't do is directly modify it as you are here (see the documentation):
document.title = 'Some Default Title'; // <=== INCORRECT
document.body = 'lorem text'; // <=== INCORRECT
document.syncWithDatabase();
setDocument(document); // <=== INCORRECT
Instead, you'd need to create a new document object
const newDoc = new Document();
newDoc.title = 'Some Default Title';
newDoc.body = 'lorem text';
newDoc.syncWithDatabase();
setDocument(newDoc);
That said, when using the useState
hook, you're usually better off keeping your state variables discrete (having one for title
and one for body
), so that changing one doesn't require also changing the other (unless, of course, they always change together). The documentation discusses that here; here's one quote:
...we remend to split state into multiple state variables based on which values tend to change together.
(their emphasis)