对此组件的使用效果

发布于 2025-02-10 02:51:21 字数 2493 浏览 2 评论 0 原文

我正在尝试使用usefect来rerender postlist (在没有删除帖子的情况下使其呈现) postScount 更改时,但我无法正确。我试图将所有内容包裹在使用效果中,但是我无法执行 addeventListener(“ click”,handingpost),因为我使用useffect等待该组件先安装,然后再安装extlistener。

父组件:

function Tabs() {
  const [posts, setPosts] = useState([]);
  const dispatch = useDispatch();

  const postsCount = useSelector((state) => state.posts.count);

  useEffect(() => {
    document.getElementById("postsTab").addEventListener("click", handlePost);
  }, [handlePost]);

  const handlePost = async (e) => {
    const { data: { getPosts: postData }} = await refetchPosts();
    setPosts(postData);
    dispatch(postActions.getPostsReducer(postData));
  };

  const { data: FetchedPostsData, refetch: refetchPosts } = useQuery( FETCH_POSTS_QUERY, { manual: true });

  const [postList, setPostsList] = useState({});

  useEffect(() => {
    setPostsList(
      <Tab.Pane>
        <Grid>
          <Grid.Column>Title</Grid.Column>
          {posts.map((post) => (
              <AdminPostsList key={post.id} postId={post.id} />
            ))}
        </Grid>
      </Tab.Pane>
    );
    console.log("changed"); //it prints "changed" everytime postCount changes (or everytime I click delete), but the component doesn't remount
  }, [postsCount]);

  const panes = [
    { menuItem: { name: "Posts", id: "postsTab", key: "posts" }, render: () => postList }
  ];

  return (<Tab panes={panes} />);
}

Child/AdminPostSlist组件:

function AdminPostsList(props) {
  const { postId } = props;
  const [deletePost] = useMutation(DELETE_POST_MUTATION, {variables: { postId } });
  const dispatch = useDispatch();

  const deletePostHandler = async () => {
    dispatch(postActions.deletePost(postId));
    await deletePost();
  };
  return (
    <>
        <Button icon="delete" onClick={deletePostHandler}></Button>
    </>
  );
}

还原器

const PostSlice = createSlice({
  name: "storePosts",
  initialState: {
    content: [],
    count: 0,
  },
  reducers: {
    getPostsReducer: (state, action) => {
      state.content = action.payload;
      state.count = action.payload.length
    },
    deletePost: (state, action) => {
      const id = action.payload
      state.content = current(state).content.filter((post) => (post.id !== id))
      state.count--
    }
  },
});

I am trying to use useEffect to rerender postList (to make it render without the deleted post) when postsCount change, but I can't get it right. I tried to wrap everything inside useEffect but I couldn't execute addEventListener("click", handlePost) because I am using useEffect to wait for this component to mount first, before attaching the evenListener.

Parent component:

function Tabs() {
  const [posts, setPosts] = useState([]);
  const dispatch = useDispatch();

  const postsCount = useSelector((state) => state.posts.count);

  useEffect(() => {
    document.getElementById("postsTab").addEventListener("click", handlePost);
  }, [handlePost]);

  const handlePost = async (e) => {
    const { data: { getPosts: postData }} = await refetchPosts();
    setPosts(postData);
    dispatch(postActions.getPostsReducer(postData));
  };

  const { data: FetchedPostsData, refetch: refetchPosts } = useQuery( FETCH_POSTS_QUERY, { manual: true });

  const [postList, setPostsList] = useState({});

  useEffect(() => {
    setPostsList(
      <Tab.Pane>
        <Grid>
          <Grid.Column>Title</Grid.Column>
          {posts.map((post) => (
              <AdminPostsList key={post.id} postId={post.id} />
            ))}
        </Grid>
      </Tab.Pane>
    );
    console.log("changed"); //it prints "changed" everytime postCount changes (or everytime I click delete), but the component doesn't remount
  }, [postsCount]);

  const panes = [
    { menuItem: { name: "Posts", id: "postsTab", key: "posts" }, render: () => postList }
  ];

  return (<Tab panes={panes} />);
}

child/AdminPostsList component:

function AdminPostsList(props) {
  const { postId } = props;
  const [deletePost] = useMutation(DELETE_POST_MUTATION, {variables: { postId } });
  const dispatch = useDispatch();

  const deletePostHandler = async () => {
    dispatch(postActions.deletePost(postId));
    await deletePost();
  };
  return (
    <>
        <Button icon="delete" onClick={deletePostHandler}></Button>
    </>
  );
}

The Reducers

const PostSlice = createSlice({
  name: "storePosts",
  initialState: {
    content: [],
    count: 0,
  },
  reducers: {
    getPostsReducer: (state, action) => {
      state.content = action.payload;
      state.count = action.payload.length
    },
    deletePost: (state, action) => {
      const id = action.payload
      state.content = current(state).content.filter((post) => (post.id !== id))
      state.count--
    }
  },
});

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

从此见与不见 2025-02-17 02:51:21

好吧,让我们在单独的评论中讨论这一点。关键点是将帖子逻辑从包装组件(TABS)中解释。您应该仅创建专门用于帖子并将其渲染的组件。像这样,您可以轻松地将与帖子相关的组件中的所有与帖子相关的逻辑隔离,例如,避免在包装器中附加一些听众(因为它不直观您在做什么,谁在听什么,因为按钮不在同一组件中) 。在分离的组件中,您只能有一个使用效果,可以提取帖子,并且您将拥有一个选择器(从Redux中选择帖子),然后只使用该选择来从组件输出内容。
该部分&lt; tab panes = {...}/&gt; 是您大多数问题的来源,因为这样您被迫求解上面的所有内容&lt; tab ../ &gt; 然后只是为了通过它,这不是最好的练习,因为它可能太复杂了(尤其是在您有多个选项卡时)。这就是为什么您需要解开并创建特定于选项卡的组件的原因。

这将是您应该如何重构的想法:

function PostsTab() {
  const posts = useSelector((state) => state.posts?.content ?? []);

  useEffect(() => {
    // Here dispatch action to load your posts
    // With this approach, when you have separated component for PostsTab no need to attach some weird event listeners, you can do everything here in effect
    // This should be triggered only once
    // You can maybe introduce 'loading' flag in your reducer so you can display some loaders for better UX
  }, []);

  return (
    <div>
      {/* Here use Tab components in order to create desired tab */}
      <Tab.Pane>
        <Grid>
          <Grid.Column>Title</Grid.Column>
          {posts.map((post) => (
            <AdminPostsList key={post.id} postId={post.id} />
          ))}
        </Grid>
      </Tab.Pane>
    </div>
  );
}

function Tabs() {
  return (
    <div>
      <PostsTab/>
      {/** HERE you can add more tabs when you need to
      * Point is to create separate component per tab so you can isolate and maintain tab state in dedicated component
      and to avoid writing all logic here in wrapper component
      * As you can see there is no need to attach any weird listener, everything related to posts is moved to PostsTab component
      */}
    </div>
  );
}

Okay, let discuss this in separate comment. Key point is to decouple posts logic from wrapper component(Tabs). You should create component dedicated only to posts and render it in wrapper. Like that you can easily isolate all posts-related logic in posts-related component, for example to avoid attaching some listeners from wrapper(because it is not intuitive what you are doing and who listens for what because button is not in that same component). In separated component you will have only one useEffect, to fetch posts, and you will have one selector(to select posts from redux), and then just use that selection to output content from component.
That part <Tab panes={...} /> was the source of most of your problems, because like that you are forced to solve everything above <Tab../> and then just to pass it, which is not best practice in you case since it can be too complicated(especially in case when you could have multiple tabs). That is why you need to decouple and to create tab-specific components.

This would be an idea of how you should refactor it:

function PostsTab() {
  const posts = useSelector((state) => state.posts?.content ?? []);

  useEffect(() => {
    // Here dispatch action to load your posts
    // With this approach, when you have separated component for PostsTab no need to attach some weird event listeners, you can do everything here in effect
    // This should be triggered only once
    // You can maybe introduce 'loading' flag in your reducer so you can display some loaders for better UX
  }, []);

  return (
    <div>
      {/* Here use Tab components in order to create desired tab */}
      <Tab.Pane>
        <Grid>
          <Grid.Column>Title</Grid.Column>
          {posts.map((post) => (
            <AdminPostsList key={post.id} postId={post.id} />
          ))}
        </Grid>
      </Tab.Pane>
    </div>
  );
}

function Tabs() {
  return (
    <div>
      <PostsTab/>
      {/** HERE you can add more tabs when you need to
      * Point is to create separate component per tab so you can isolate and maintain tab state in dedicated component
      and to avoid writing all logic here in wrapper component
      * As you can see there is no need to attach any weird listener, everything related to posts is moved to PostsTab component
      */}
    </div>
  );
}
So要识趣 2025-02-17 02:51:21

好的,让我们讨论我对未来读者做错了什么:

  1. 无需使用这种奇怪的意大利面条,
useEffect(() => {
  document.getElementById("postsTab").addEventListener("click", handlePost);
}, [handlePost]);
const panes = [
  { menuItem: { name: "Posts", id: "postsTab", key: "posts" }, render: () => postList }
];

因为我可以使用&lt;菜单。 ; 直接连接 onclick

  1. 我必须使用使用来监视 ports 依赖项,但是 .map()将自动更新其内容,如果数组我正在映射有任何更改,因此在此上下文中无需使用使用


  2. 我认为我可以从子组件中使用升降状态到 setPost ,更改将触发 .map()重新映射并弹出已删除的元素,但是我可以't找到一种方法,所以我使用的是redux(存储帖子)和 useffect 将 ports 分配给商店,而不是我正在映射存储的redux元素,如果这是最好的方法,但这是我设法做的。

  3. 我几乎尝试过的最重要的事情是,我必须使用 proxy.readquery 在添加/删除帖子时更新Apollo-cache
    这就是我的做法

  const [posts, setPosts] = useState([]);

  const handlePosts = async () => {
    const { data: { getPosts: postData } } = await refetchPosts();
    setPosts(postData);
  };

  const handlePosts = async () => {
    const { data } = await refetchPosts();
    setPosts(data.getPosts);
  };

  // Using useEffect temporarily to make it work.
  // Will replace it with an lifting state when refactoring later.
  useEffect(() => {
    posts && dispatch(postsActions.PostsReducer(posts))
  }, [posts]);
  const [deletePost] = useMutation(DELETE_POST_MUTATION, {
    update(proxy) {
      let data = proxy.readQuery({
        query: FETCH_POSTS_QUERY,
      });
      // Reconstructing data, filtering the deleted post 
      data = { getPosts: data.getPosts.filter((post) => post.id !== postId) };
      // Rewriting apollo-cache
      proxy.writeQuery({ query: FETCH_POSTS_QUERY, data });
    },
    onError(err) {
      console.log(err);
    },
    variables: { postId },
  });

  const deletePostHandler = async () => {
    deletePost();
    dispatch(postsActions.deletePost(postId))
  };

感谢 @ anuj panwar @ milos pavlovic 帮助您提供帮助,kudos to @引起了我的注意

Ok, let's discuss what I did wrong for the future reader:

  1. There is no need to use this weird spaghetti
useEffect(() => {
  document.getElementById("postsTab").addEventListener("click", handlePost);
}, [handlePost]);
const panes = [
  { menuItem: { name: "Posts", id: "postsTab", key: "posts" }, render: () => postList }
];

for I could've used a <Menu.Item onClick={handleClick}>Posts</Menu.Item> to attach the onClick directly.

  1. I had to use useEffect to monitor posts dependency, but .map() will automatically update its content if the array I am mapping had any changes so there is no need to use it use useEffect in this context.

  2. I think I can use lifting state to setPosts from the child component and the change will trigger .map() to remap and pop the deleted element, but I couldn't find a way to so, so I am using a combination of redux (to store the posts) and useEffect to dispatch the posts to the store than I am mapping over the stored redux element, idk if this is the best approach but this is all I managed to do.

  3. The most important thing I didn't notice when I almost tried everything is, I must update apollo-cache when adding/deleting a post, by using proxy.readQuery
    this is how I did it

  const [posts, setPosts] = useState([]);

  const handlePosts = async () => {
    const { data: { getPosts: postData } } = await refetchPosts();
    setPosts(postData);
  };

  const handlePosts = async () => {
    const { data } = await refetchPosts();
    setPosts(data.getPosts);
  };

  // Using useEffect temporarily to make it work.
  // Will replace it with an lifting state when refactoring later.
  useEffect(() => {
    posts && dispatch(postsActions.PostsReducer(posts))
  }, [posts]);
  const [deletePost] = useMutation(DELETE_POST_MUTATION, {
    update(proxy) {
      let data = proxy.readQuery({
        query: FETCH_POSTS_QUERY,
      });
      // Reconstructing data, filtering the deleted post 
      data = { getPosts: data.getPosts.filter((post) => post.id !== postId) };
      // Rewriting apollo-cache
      proxy.writeQuery({ query: FETCH_POSTS_QUERY, data });
    },
    onError(err) {
      console.log(err);
    },
    variables: { postId },
  });

  const deletePostHandler = async () => {
    deletePost();
    dispatch(postsActions.deletePost(postId))
  };

Thanks to @Anuj Panwar @Milos Pavlovic for helping out, kudos to @Cptkrush for bringing the store idea into my attention

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文