I’m new to React. Here is the scenario:
- I have a class called Book. A book has a title and content, both strings.
- I’m trying to separate the concerns within the book page into a BookComponent (view) and Book hook (state) and a Book class (dealing with the backend API), similar to what’s described here: https://martinfowler.com/articles/modularizing-react-apps.html
Here is a sample code:
export const useBook = () => {
const [book, setBook] = React.useState<Book>(new Book('', ''))
function load(id: string) {
Book.fetch(id).then((book) => { setBook(book) })
}
function title() : string {
return book.title
}
function content() : string {
return book.content
}
function setTitle(title: string) {
setBook({ ...book, title })
}
function setContent(content: string) {
setBook({ ...book, content })
}
return {
title,
setTitle,
content,
setContent
}
}
In the BookComponent (view) I have this:
<div>{book.title()}</div>
<input type="text" value={book.content()} onChange={(e) => book.setContent(e.target.value) }} />
(Omitted useEffect code to load the book)
The page shows the title and the content correctly. However, as soon as I type anything in the textbox, the title goes back to "" which is the initial value of the state.
I thought using setBook({...book, content}) within the hook is going to copy the existing book state and only modify content, but it seems the code enters setContent with a brand new book when I inspect ...book
Changing the state to the following fixes the problem:
const [book, setBook] = React.useState<Book | null>(null)
// ...
function title() : string {
if (!book) return { '' }
return book.title
}
// same for content
But I don’t understand why the state would start from a null value (default) every time and the lifecycle of it.
>Solution :
Set your new state like this (with a callback fn) inside your setContent (and similarly in setTitle) with an access to previous state and it should work exactly as you expected. Copy prevState with spread operateor into a new state and override content:
setBook(prevState => ({ ...prevState, content }))