I have a React 18 app. I am using @tanstack/react-query
, version 5 for state management.
Firstly, when I click the like button, my UI does not change. Typical behavior should be that the number of likes should increase by one.
Secondly, when I reload my page I get the below errors in my browser console and my browser view is blank.
Not sure, what I am doing wrong here.
Below is my code:
App.jsx
file:
import { useState } from "react";
import MenuBar from "./components/MenuBar";
import Notification from "./components/Notification";
import BlogsPage from "./pages/BlogsPage";
import LoginPage from "./pages/LoginPage";
import UsersPage from "./pages/UsersPage";
import { Route, Routes, useMatch } from "react-router-dom";
import User from "./components/User";
import { useQueryClient } from "@tanstack/react-query";
import Blog from "./components/Blog";
const App = () => {
const queryClient = useQueryClient();
const allBlogs = queryClient.getQueryData(['blogs']);
const allUsers = queryClient.getQueryData(['users']);
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const userMatch = useMatch("/users/:id");
console.log('userMatch', userMatch);
const singleUserInfo = userMatch
? allUsers.find(user => user.id === userMatch.params.id)
: null;
console.log('singleUserInfo', singleUserInfo);
const blogMatch = useMatch("/blogs/:id");
console.log('blogMatch', blogMatch);
const singleBlogInfo = blogMatch
? allBlogs.find(blog => blog.id === blogMatch.params.id)
: null;
console.log('singleBlogInfo', singleBlogInfo);
return (
<div>
<h1>Blogs App</h1>
<MenuBar setUsername={setUsername} setPassword={setPassword} />
<Notification />
<LoginPage
username={username}
password={password}
setUsername={setUsername}
setPassword={setPassword}
/>
<Routes>
<Route path="/users/:id" element={<User user={singleUserInfo} />} />
<Route path="/blogs/:id" element={<Blog blog={singleBlogInfo} />} />
<Route path="/users" element={<UsersPage />} />
<Route path="/" element={<BlogsPage />} />
</Routes>
</div>
);
};
export default App;
Blog.jsx
file:
import PropTypes from "prop-types";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useNotificationDispatch } from "../context/NotificationContext";
import blogService from "../services/blogs";
import { removeNotificationAfterFiveSeconds, setNotification } from "../helper/notificationSetOrRemove";
import { useUserValue } from "../context/UserContext";
import { Link } from "react-router-dom";
const Blog = ({ blog }) => {
console.log('Blog.jsx', blog);
const queryClient = useQueryClient();
const notificationDispatch = useNotificationDispatch();
const userLoggedIn = useUserValue();
const updateBlogMutation = useMutation({
mutationFn: blogService.updateBlog,
// mutationFn: (blog) => blogService.updateBlog(blog),
onSuccess: (updatedBlog) => {
// queryClient.invalidateQueries('blogs');
queryClient.invalidateQueries({ queryKey: ['blogs'] }, updatedBlog);
},
onError: (error, blogData) => {
console.error(`UPDATE MUTATION ERROR: ${error.message}`);
setNotification(notificationDispatch, `UPDATE MUTATION ERROR: Blog '${blogData.title}' was already removed from server`, true);
removeNotificationAfterFiveSeconds(notificationDispatch);
}
});
const handleIncreaseLikes = () => {
updateBlogMutation.mutate({ ...blog, likes: blog.likes + 1 });
};
return (
<div>
<h1 data-testid="title">{blog.title}</h1>
<h4 data-testid="author">Author: {blog.author}</h4>
<div data-testid="url">
<Link to={blog.url} target="_blank">{blog.url}</Link>
</div>
<div>
<div>
<div>
<span data-testid="likes">Likes: {blog.likes}</span>
<button onClick={handleIncreaseLikes} className="likeBtn">
like
</button>
</div>
<div data-testid="addedBy">Added by: {blog.user.name}</div>
</div>
</div>
</div>
);
};
Blog.propTypes = {
blog: PropTypes.shape({
id: PropTypes.string,
title: PropTypes.string.isRequired,
author: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
likes: PropTypes.number,
user: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
}),
}),
};
export default Blog;
/src/services/blogs.js
file:
import axios from 'axios';
const baseUrl = '/api/blogs';
const updateBlog = async (updatedBlog) => {
const response = await axios.put(`${baseUrl}/${updatedBlog.id}`, updatedBlog);
return response.data;
};
export default { updateBlog };
BlogList.jsx
file:
import { useQuery } from "@tanstack/react-query";
import blogService from "../services/blogs";
import { Link } from "react-router-dom";
const BlogsList = () => {
// styles
const blogStyle = {
padding: 5,
border: "solid",
borderWidth: 1,
marginBottom: 5,
};
// get all blogs
const { isLoading, isError, data: blogsData, error } = useQuery({
queryKey: ['blogs'],
queryFn: blogService.getAllBlogs,
select: (blogs) => blogs.sort((a, b) => b.likes - a.likes),
retry: false,
});
if (isLoading) {
return <div>loading data...</div>;
}
if (isError) {
return (
<>
<div>blogs service not available due to: {error.message}.</div>
<div>Unable to get all blogs</div>
</>
);
}
// console.log('blogsData', blogsData);
return (
<div>
<ul style={{ listStyleType: "none", padding: 0 }}>
{blogsData.map((blog) => (
<li key={blog.id} style={blogStyle} className="blog">
<Link to={`/blogs/${blog.id}`}>{blog.title}</Link> - <span>{blog.author}</span>
</li>
))}
</ul>
</div>
);
}
export default BlogsList;
useQueryClient
is not reactive to any query data changing. The standard way is to call useQuery
to retrieve any data (which handles caching automatically).
const allBlogs = useQuery({
queryKey: ['blogs'],
queryFn: () => {/* make request to get blog data*/}
}).data;