使用 Redux Toolkit 时,两个不同页面中的数据不会立即在 UI 上更新

问题描述 投票:0回答:1

我有一个 React 18 应用程序,我正在使用 Redux Toolkit 进行状态管理。

下面是用户页面上博客应用程序的图像。当我在博客页面上添加/创建新博客时,新博客会正确添加。但是,当我导航到用户页面时,框中的用户应该有 3 个博客,而不是 2 个。因此,用户创建的博客数量不会立即更新。但是,当我重新加载页面时,用户页面会向我显示新的博客数量,即框中的用户为 3。

enter image description here

问题: 如何在不重新加载的情况下添加新博客时让用户页面也做出反应?

以下是我的一些文件。

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;
javascript reactjs redux redux-toolkit react-18
1个回答
0
投票

我想我找到了解决方案。我在创建博客和删除污点后发送

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;
© www.soinside.com 2019 - 2024. All rights reserved.