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 ponents, I have a <Books />
ponent which renders a <Book />
ponent for every book in my library database.
Here are my ponents:
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>
</>
);
}
}
Problem:
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.
Slow solution: Do not use next/link.
Replace
<Link href={`/books/${book.slug}`}>
<a>{book.title}</a>
</Link>
with
<a href={`/books/${book.slug}`}>{book.title}</a>
Fast solution: Keep using next/link?
Is there a way to use next/link and maintain state when navigating to another pre-rendered route?
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 ponents, I have a <Books />
ponent which renders a <Book />
ponent for every book in my library database.
Here are my ponents:
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>
</>
);
}
}
Problem:
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.
Slow solution: Do not use next/link.
Replace
<Link href={`/books/${book.slug}`}>
<a>{book.title}</a>
</Link>
with
<a href={`/books/${book.slug}`}>{book.title}</a>
Fast solution: Keep using next/link?
Is there a way to use next/link and maintain state when navigating to another pre-rendered route?
Share Improve this question edited Oct 30, 2020 at 20:12 fredperk asked Sep 1, 2020 at 13:07 fredperkfredperk 8182 gold badges11 silver badges28 bronze badges 2- This is why REST uses DELETE method and HTTP_NO_CONTENT as response, so consumers will fetch affected object again from the detail view or do the math themselves. Please look into generics and don't implement half of the stuff that's there for you yourself. – user1600649 Commented Oct 29, 2020 at 17:33
- And second, you don't need signals. You can just override Like.delete() and update refs before you call super().delete(). – user1600649 Commented Oct 29, 2020 at 17:34
4 Answers
Reset to default 4TLDR: 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
ponent 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 ponents. Any time a child ponent 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 ponent is reusable, then you'll conditionally render it using this.props.book
(which es from a parent container) or the child ponent 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 plexity because you have to make sure that any parent state changes won't rerender the child ponent 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 ponents that can be utilized by one or many NextJS project pages or ponents.
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
ponent to the Book
ponent.
Add a ponentDidUpdate()
method to your book ponent.
class Book extends React.Component {
constructor(props, context) {
super(props, context);
this.state = { liked: this.props.liked };
}
ponentDidUpdate(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>
</>
);
}
}
In DeleteLikeView class you get book object. it retrieved from DB and saved in book variable. when you deleted like object num_of_likes attribute has been updated in DB but your variable still consists previous object. after like.delete() mand you should get object again and print num_of_likes att. It is as your expected.