我有一个 React 18 应用程序,我正在使用 Redux Toolkit 进行状态管理。
下面是用户页面上博客应用程序的图像。当我在博客页面上添加/创建新博客时,新博客会正确添加。但是,当我导航到用户页面时,框中的用户应该有 3 个博客,而不是 2 个。因此,用户创建的博客数量不会立即更新。但是,当我重新加载页面时,用户页面会向我显示新的博客数量,即框中的用户为 3。
问题: 如何在不重新加载的情况下添加新博客时让用户页面也做出反应?
以下是我的一些文件。
blogReducer.js
文件:
import { createSlice } from '@reduxjs/toolkit';
import blogService from '../services/blogs';
import { setNotification } from './notificationReducer';
const blogSlice = createSlice({
name: 'blogs',
initialState: [],
reducers: {
setBlogs(state, action) {
const sortedBlogs = [...action.payload].sort((a, b) => b.likes - a.likes);
return sortedBlogs;
},
appendBlog(state, action) {
state.push(action.payload);
},
increaseLikes(state, action) {
const id = action.payload;
const blogToVote = state.find((blog) => blog.id === id);
const blogUpdated = {
...blogToVote,
likes: blogToVote.likes + 1,
};
const blogList = state.map((blog) =>
blog.id !== id ? blog : blogUpdated,
);
const sortedBlogs = blogList.sort((a, b) => b.likes - a.likes);
return sortedBlogs;
},
removeBlog(state, action) {
console.log('removeBlog action payload from blogReducer', action.payload);
return state.filter((blog) => blog.id !== action.payload);
},
},
});
export const { setBlogs, appendBlog, increaseLikes, removeBlog } =
blogSlice.actions;
export const initializeBlogs = () => {
return async (dispatch) => {
console.log('initializing blogs from blogReducer');
const blogs = await blogService.getAllBlogs();
dispatch(setBlogs(blogs));
};
};
export const createBlog = (blog) => {
console.log('creating blog');
return async (dispatch) => {
try {
const newBlog = await blogService.createBlog(blog);
dispatch(appendBlog(newBlog));
dispatch(
setNotification(
{ message: `SUCCESS: Added ${newBlog.title} blog.`, isError: false },
5000,
),
);
} catch (error) {
console.log('ERROR when creating blog: ', error.message);
dispatch(
setNotification(
{ message: error.response.data.error, isError: true },
5000,
),
);
}
};
};
export const voteBlog = (id, title) => {
console.log('voting blog');
return async (dispatch) => {
try {
const blogToVote = await blogService.getBlogById(id);
console.log('voted blog', blogToVote);
const blogUpdated = {
...blogToVote,
likes: blogToVote.likes + 1,
};
await blogService.updateBlog(blogUpdated);
dispatch(increaseLikes(id));
} catch (error) {
console.log('ERROR when voting blog: ', error.message);
dispatch(
setNotification(
{
message: `Blog '${title}' was removed from the server.`,
isError: true,
},
5000,
),
);
}
};
};
export const deleteBlogById = (id) => {
console.log('deleting blog');
return async (dispatch) => {
try {
await blogService.deleteBlog(id);
console.log('deleted blog');
dispatch(removeBlog(id));
} catch (error) {
console.log('ERROR when deleting blog: ', error.message);
alert('Sorry! Blog was already removed from the server.');
}
};
};
export default blogSlice.reducer;
userReducer.js
文件:
import { createSlice } from "@reduxjs/toolkit";
import userService from '../services/users';
const userSlice = createSlice({
name: 'users',
initialState: [],
reducers: {
setUsers(state, action) {
const sortedUsers = [...action.payload].sort();
return sortedUsers;
}
},
});
export const { setUsers } = userSlice.actions;
export const initializeUsers = () => {
return async (dispatch) => {
console.log('initializing users from userReducer');
const users = await userService.getAllUsers();
dispatch(setUsers(users));
}
}
export default userSlice.reducer;
loginReducer.js
文件:
import { createSlice } from '@reduxjs/toolkit';
const loginSlice = createSlice({
name: 'loggedInUser',
initialState: null,
reducers: {
setLoginUser(state, action) {
return action.payload;
},
setLogoutUser() {
return null;
},
},
});
export const { setLoginUser, setLogoutUser } = loginSlice.actions;
export default loginSlice.reducer;
App.jsx
文件:
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Route, Routes } from "react-router-dom";
import { initializeBlogs } from './reducers/blogReducer';
import { initializeUsers } from './reducers/userReducer';
import Notification from './components/Notification';
import LoginPage from './pages/LoginPage';
import BlogsPage from './pages/BlogsPage';
import UsersPage from './pages/UsersPage';
import MenuBar from './components/MenuBar';
const App = () => {
const dispatch = useDispatch();
const loggedInUserFromStore = useSelector(({ loggedInUser }) => loggedInUser);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
useEffect(() => {
if (loggedInUserFromStore) {
dispatch(initializeBlogs());
dispatch(initializeUsers());
}
}, [loggedInUserFromStore, dispatch]);
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' element={loggedInUserFromStore && <UsersPage /> } />
<Route path='/' element={loggedInUserFromStore && <BlogsPage /> } />
</Routes>
</div>
);
};
export default App;
BlogsPage.jsx
文件:
import { useRef } from 'react';
import Togglable from '../components/Togglable';
import NewBlogForm from '../components/NewBlogForm';
import BlogsList from '../components/BlogsList';
import { useDispatch } from 'react-redux';
import { createBlog } from '../reducers/blogReducer';
const BlogsPage = () => {
const dispatch = useDispatch();
const blogFormRef = useRef();
const addBlog = (newBlog) => {
blogFormRef.current.toggleVisibility();
dispatch(createBlog(newBlog));
};
const blogForm = () => (
<Togglable buttonLabel="new blog" ref={blogFormRef}>
<NewBlogForm createBlog={addBlog} />
</Togglable>
);
return (
<>
{blogForm()}
<BlogsList />
</>
);
}
export default BlogsPage;
BlogsList.jsx
文件:
import { useSelector } from 'react-redux';
import Blog from './Blog';
const BlogsList = () => {
const blogsFromStore = useSelector(({ blogs }) => blogs);
return (
<>
<h1>Blogs</h1>
<ul style={{ listStyleType: 'none', padding: 0 }}>
{blogsFromStore.map((blog) => (
<Blog key={blog.id} blog={blog} />
))}
</ul>
</>
);
}
export default BlogsList;
Blog.jsx
文件:
import { useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { deleteBlogById, voteBlog } from '../reducers/blogReducer';
const Blog = ({ blog }) => {
// styles
const blogStyle = {
paddingLeft: 10,
paddingRight: 10,
paddingBottom: 10,
border: 'solid',
borderWidth: 1,
marginBottom: 5,
};
const removeBtnStyle = {
backgroundColor: 'lightblue',
padding: 5,
borderRadius: 10,
fontWeight: 'bold',
};
const [viewBlogInfo, setViewBlogInfo] = useState(false);
const dispatch = useDispatch();
const loggedInUserFromStore = useSelector(({ loggedInUser }) => loggedInUser);
const handleLikes = () => {
dispatch(voteBlog(blog.id, blog.title));
};
const handleDeleteBlog = () => {
const toDelete = window.confirm(`Delete "${blog.title}" blog?`);
console.log({ toDelete });
if (toDelete) {
dispatch(deleteBlogById(blog.id));
}
};
const toggleShow = () => {
setViewBlogInfo(!viewBlogInfo);
};
return (
<li style={blogStyle} className="blog">
<p>
<span data-testid="title">{blog.title} </span>
<button onClick={toggleShow} className="viewHideBtn">
{viewBlogInfo ? 'hide' : 'view'}
</button>
</p>
{viewBlogInfo && (
<div>
<div>
<div>Author: {blog.author}</div>
<div>
<span data-testid="likes">Likes: {blog.likes}</span>
<button onClick={handleLikes} className="likeBtn">
like
</button>
</div>
<div>URL: {blog.url}</div>
<div>Added by: {blog.user.name}</div>
</div>
<br />
{blog.user.username === loggedInUserFromStore.username && (
<div>
<button
style={removeBtnStyle}
onClick={handleDeleteBlog}
className="removeBtn"
>
remove
</button>
</div>
)}
</div>
)}
</li>
);
};
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,
}),
}).isRequired,
};
export default Blog;
NewBlogForm.jsx
文件:
import { useState } from 'react';
import PropTypes from 'prop-types';
const NewBlogForm = ({ createBlog }) => {
const [newBlogTitle, setNewBlogTitle] = useState('');
const [newBlogAuthor, setNewBlogAuthor] = useState('');
const [newBlogUrl, setNewBlogUrl] = useState('');
const addBlog = (event) => {
event.preventDefault();
createBlog({
title: newBlogTitle,
author: newBlogAuthor,
url: newBlogUrl,
});
setNewBlogTitle('');
setNewBlogAuthor('');
setNewBlogUrl('');
};
return (
<div>
<h2>Create a new blog</h2>
<form onSubmit={addBlog}>
<div>
title:
<input
data-testid="blog-title"
value={newBlogTitle}
onChange={(event) => setNewBlogTitle(event.target.value)}
name="title"
id="blog-title"
/>
</div>
<div>
author:
<input
data-testid="blog-author"
value={newBlogAuthor}
onChange={(event) => setNewBlogAuthor(event.target.value)}
name="author"
id="blog-author"
/>
</div>
<div>
url:
<input
data-testid="blog-url"
value={newBlogUrl}
onChange={(event) => setNewBlogUrl(event.target.value)}
name="url"
id="blog-url"
/>
</div>
<button type="submit">create</button>
</form>
</div>
);
};
NewBlogForm.propTypes = {
createBlog: PropTypes.func.isRequired,
};
export default NewBlogForm;
UsersPage.jsx
文件:
import UsersList from "../components/UsersList";
const UsersPage = () => {
return <UsersList />;
}
export default UsersPage;
UsersList.jsx
文件:
import { useSelector } from "react-redux";
const UsersList = () => {
const usersFromStore = useSelector(({ users }) => users);
console.log('<UsersList /> component - usersFromStore: ', usersFromStore);
return (
<>
<h1>Users</h1>
<table>
<thead>
<tr>
<th>names</th>
<th>blogs created</th>
</tr>
</thead>
<tbody>
{usersFromStore.map(user => (
<tr key={user.id}>
<td>{user.name || user.username}</td>
<td>{user.blogs.length}</td>
</tr>
))}
</tbody>
</table>
</>
);
}
export default UsersList;
我想我找到了解决方案。我在创建博客和删除污点后发送
initializeUsers
。请参阅下面的 blogReducer.js
文件中所做的更改:
import { createSlice } from '@reduxjs/toolkit';
import blogService from '../services/blogs';
import { setNotification } from './notificationReducer';
import { initializeUsers } from './userReducer'; // NEW LINE ADDED - Re-fetch users
const blogSlice = createSlice({
name: 'blogs',
initialState: [],
reducers: {
...
},
});
export const { setBlogs, appendBlog, increaseLikes, removeBlog } =
blogSlice.actions;
export const initializeBlogs = () => {
...
};
export const createBlog = (blog) => {
console.log('creating blog');
return async (dispatch) => {
try {
const newBlog = await blogService.createBlog(blog);
dispatch(appendBlog(newBlog));
dispatch(
setNotification(
{ message: `SUCCESS: Added ${newBlog.title} blog.`, isError: false },
5000,
),
);
dispatch(initializeUsers()); // NEW LINE ADDED - Re-fetch users
} catch (error) {
console.log('ERROR when creating blog: ', error.message);
dispatch(
setNotification(
{ message: error.response.data.error, isError: true },
5000,
),
);
}
};
};
export const voteBlog = (id, title) => {
...
};
export const deleteBlogById = (id) => {
console.log('deleting blog');
return async (dispatch) => {
try {
await blogService.deleteBlog(id);
console.log('deleted blog');
dispatch(removeBlog(id));
dispatch(initializeUsers()); // NEW LINE ADDED - Re-fetch users
} catch (error) {
console.log('ERROR when deleting blog: ', error.message);
alert('Sorry! Blog was already removed from the server.');
}
};
};
export default blogSlice.reducer;