I am developing a library Next.js application. For the purposes of this question, I have two pages in my application: BooksPage which lists all books, and BookPage which renders details of a book. In terms of components, I have a <Books />
component which renders a <Book />
component for every book in my library database.
Here are my components:
Books.js:
function Books({ books }) {
return (
<>
{books.map(book => (
<Book key={book.id} book={book} />
))}
</>
);
}
Book.js:
class Book extends React.Component {
constructor(props, context) {
super(props, context);
this.state = { liked: false };
}
like = () => {
this.setState({ liked: this.state.liked ? false : true })
};
render() {
return (
<>
<Link href={`/books/${book.slug}`}>
<a>{book.title}</a>
</Link>
<Button onClick={this.like}>
<LikeIcon
className={this.state.liked ? "text-success" : "text-dark"}
/>
</Button>
</>
);
}
}
Say that I am on page BooksPage. When I click the like button of a <Book />
the icon color toggles properly in the frontend and the like is successfully added or removed in the backend. When I refresh BooksPage all the state is maintained and consistent.
The problem arises when I like something on BooksPage and then immediately navigate to BookPage without refreshing using next/link. There the like button is not toggled consistently and the state from BooksPage is lost. Notice that if I hard-refresh the page everything goes back to normal.
Replace
<Link href={`/books/${book.slug}`}>
<a>{book.title}</a>
</Link>
with
<a href={`/books/${book.slug}`}>{book.title}</a>
Is there a way to use next/link and maintain state when navigating to another pre-rendered route?
You need to use Model.refresh_from_db(...)
--(Django Doc) method to retrieve the updated value from the Database
class DeleteLikeView(APIView):
def post(self, request, book):
book = get_object_or_404(Book, id=book)
print(book.num_of_likes)
like = Like.objects.get(user=request.user, book=book)
like.delete()
book.refresh_from_db() # here is the update
print(book.num_of_likes) # You will get the updated value
return ...
Pass the liked
property of a book somehow through the API. Then, pass that prop down from the Books
component to the Book
component.
Add a componentDidUpdate()
method to your book component.
class Book extends React.Component {
constructor(props, context) {
super(props, context);
this.state = { liked: this.props.liked };
}
componentDidUpdate(prevProps) {
if (this.props.liked !== prevProps.liked) {
this.setState({
liked: this.props.liked,
});
}
}
like = () => {
this.setState({ liked: !this.state.liked })
};
render() {
return (
<>
<Link href={`/books/${book.slug}`}>
<a>{book.title}</a>
</Link>
<Button onClick={this.like}>
<LikeIcon
className={this.state.liked ? "text-success" : "text-dark"}
/>
</Button>
</>
);
}
}
TLDR: Any time the button needs to be changed, the API must change data, and it must update the closest parent's local state to update the button
's appearance. The API will control all aspects of local state. You can't update local state unless an API request is successful. Therefore, the client and API are always 1:1.
The Button
component in Book.js should NOT be maintaining its own state separately from the API data; instead, wherever you're fetching book
data from the API, it should also be controlling the button's state (and its appearance). Why? Because with the current approach, the API request can fail, but the client will still update. As a result, the API and client may no longer be 1:1 (client shows liked, but API still shows that it's disliked/unliked).
In practice, you'll have a parent container that acts like a state manager. It fetches all relevant data, handles updates to the data, and displays the results using stateless child components. Any time a child component needs to be updated (such displaying a "like" or "dislike" button based upon a button press), it must first make an API request to change the data, then the API must respond with relevant data to the update the state used by the child:
Alternatively, if this component is reusable, then you'll conditionally render it using this.props.book
(which comes from a parent container) or the child component must request data from an API to update its own local this.state.book
. Same as the above diagram, the API requests control all aspects of state changes:
There's yet another approach that is the same as the diagram above, but it uses the child local state regardless of the parent's state. This child state will only be updated by its own class methods. This introduces a bit more complexity because you have to make sure that any parent state changes won't rerender the child component with stale data. Ultimately, which approach to take is up to you.
On a side note: This question doesn't make much contextual sense as libraries don't render pages nor do they attempt internal page navigations. Instead, they offer reusable components that can be utilized by one or many NextJS project pages or components.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With