【问题标题】:Sibling component not re-rerendering on state change (using useEffect, useState and Context)兄弟组件不会在状态更改时重新渲染(使用 useEffect、useState 和 Context)
【发布时间】:2021-01-09 18:31:45
【问题描述】:

在我的 Main.js 中,我使用用户名和我关注的用户列表创建了第一个全局状态。

然后,Wall 组件和 FollowSidebar 都会渲染关注列表及其消息(加上主用户的消息)。

到目前为止一切顺利。但是在 FollowSidebar 内一个名为 FollowUser 的嵌套组件中,我有一个 onClick 来删除用户。我的理解是,因为我改变了状态,useEffect 会负责 Wall 组件重新渲染它,但什么也没有发生......我已经在网上检查了几个例子,但到目前为止没有任何帮助我的用例。

不用说我对 React 的经验并不多,而且 Hooks 有点复杂。

这里的代码:

Main.js:

import React, { useEffect, useState } from "react";
import ReactDom from "react-dom";
import db from "./firebase.js";

// Components
import Header from "./components/Header";
import FollowingSidebar from "./components/FollowingSidebar";
import SearchUsers from "./components/SearchUsers";
import NewMessageTextarea from "./components/NewMessageTextarea";
import Wall from "./components/Wall";

// Context
import StateContext from "./StateContext";

function Main() {
  const [mainUser] = useState("uid_MainUser");
  const [follows, setFollows] = useState([]);

  const setInitialFollows = async () => {
    let tempFollows = [mainUser];
    const user = await db.collection("users").doc(mainUser).get();
    user.data().following.forEach(follow => {
      tempFollows.push(follow);
    });
    setFollows(tempFollows);
  };

  useEffect(() => {
    setInitialFollows();
  }, []);

  const globalValues = {
    mainUserId: mainUser,
    followingUsers: follows
  };

  return (
    <StateContext.Provider value={globalValues}>
      <Header />
      <FollowingSidebar />
      <SearchUsers />
      <NewMessageTextarea />
      <Wall />
    </StateContext.Provider>
  );
}

ReactDom.render(<Main />, document.getElementById("app"));

if (module.hot) {
  module.hot.accept();
}

FollowingSidebar 组件:

import React, { useState, useEffect, useContext } from "react";
import db from "../firebase.js";
import StateContext from "../StateContext";
import FollowingUser from "./FollowingUser";

export default function FollowingSidebar() {
  const { followingUsers } = useContext(StateContext);
  const [users, setUsers] = useState(followingUsers);

  useEffect(() => {
    const readyToRender = Object.values(followingUsers).length > 0;
    if (readyToRender) {
      db.collection("users")
        .where("uid", "in", followingUsers)
        .get()
        .then(users => {
          setUsers(users.docs.map(user => user.data()));
        });
    }
  }, [followingUsers]);

  return (
    <section id="following">
      <div className="window">
        <h1 className="window__title">People you follow</h1>
        <div className="window__content">
          {users.map((user, index) => (
            <FollowingUser avatar={user.avatar} username={user.username} uid={user.uid} key={index} />
          ))}
        </div>
      </div>
    </section>
  );
}

FollowingUser 组件:

import React, { useState, useContext } from "react";
import db from "../firebase.js";
import firebase from "firebase";
import StateContext from "../StateContext";

export default function FollowingUser({ avatar, username, uid }) {
  const { mainUserId, followingUsers } = useContext(StateContext);
  const [follows, setFollows] = useState(followingUsers);

  const removeFollow = e => {
    const userElement = e.parentElement;
    const userToUnfollow = userElement.getAttribute("data-uid");

    db.collection("users")
      .doc(mainUserId)
      .update({
        following: firebase.firestore.FieldValue.arrayRemove(userToUnfollow)
      })
      .then(() => {
        const newFollows = follows.filter(follow => follow !== userToUnfollow);
        setFollows(newFollows);
      });
    userElement.remove();
  };

  return (
    <article data-uid={uid} className="following-user">
      <figure className="following-user__avatar">
        <img src={avatar} alt="Profile picture" />
      </figure>
      <h2 className="following-user__username">{username}</h2>
      <button>View messages</button>
      {uid == mainUserId ? "" : <button onClick={e => removeFollow(e.target)}>Unfollow</button>}
    </article>
  );
}

墙组件:

import React, { useState, useEffect, useContext } from "react";
import db from "../firebase.js";
import Post from "./Post";
import StateContext from "../StateContext";

export default function Wall() {
  const { followingUsers } = useContext(StateContext);
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    console.log(followingUsers);
    const readyToRender = Object.values(followingUsers).length > 0;
    if (readyToRender) {
      db.collection("posts")
        .where("user_id", "in", followingUsers)
        .orderBy("timestamp", "desc")
        .get()
        .then(posts => setPosts(posts.docs.map(post => post.data())));
    }
  }, [followingUsers]);

  return (
    <section id="wall">
      <div className="window">
        <h1 className="window__title">Latest messages</h1>
        <div className="window__content">
          {posts.map((post, index) => (
            <Post avatar={post.user_avatar} username={post.username} uid={post.user_id} body={post.body} timestamp={post.timestamp.toDate().toDateString()} key={index} />
          ))}
        </div>
      </div>
    </section>
  );
}

StateContext.js:

import { createContext } from "react";

const StateContext = createContext();

export default StateContext;

【问题讨论】:

  • 你是如何实现 StateContext 的?
  • @ImranRafiqRather 我已将其添加到主要消息的末尾。
  • 嗯。是的,我检查了..让我再次检查代码:) 我希望所有数据都正确地来自 firestore...
  • 是的。我现在看到的是,由于某种原因,FollowingUser 中的 setFollows 实际上并没有更新状态。
  • 我创建了一个类似的演示 :) 给我一点时间看看 Mate 发生了什么 :)

标签: javascript reactjs react-hooks use-effect use-state


【解决方案1】:

主要问题是Main.js文件中state变量的设置(这个数据实际上应该是Context的一部分以全局处理状态)。

以下代码不会全局更新我们的状态。

   const globalValues = {
     mainUserId: mainUser,
     followingUsers: follows
   };

我们必须以一种在全局上下文级别上对其进行修改的方式来编写状态。 所以在你的Main.js 设置状态如下:

  const [globalValues, setGlobalValues] = useState({
    mainUserId: "uid_MainUser",
    followingUsers: []
  });

还将您所有的 event handlers 添加到 Main.js 中的 Context Level 中,只是为了避免 prop-drilling 并更好地工作。

CODESAND BOX DEMO:https://codesandbox.io/s/context-api-and-rendereing-issue-uducc

代码片段演示:

import React, { useEffect, useState } from "react";
import FollowingSidebar from "./FollowingSidebar";

import StateContext from "./StateContext";
const url = "https://jsonplaceholder.typicode.com/users";

function App() {
  const [globalValues, setGlobalValues] = useState({
    mainUserId: "uid_MainUser",
    followingUsers: []
  });

  const getUsers = async (url) => {
    const response = await fetch(url);
    const data = await response.json();

    setGlobalValues({
      ...globalValues,
      followingUsers: data
    });
  };

  // Acts similar to componentDidMount now :) Called only initially
  useEffect(() => {
    getUsers();
  }, []);

  const handleClick = (id) => {
    console.log(id);
    const updatedFollowingUsers = globalValues.followingUsers.filter(
      (user) => user.id !== id
    );
    setGlobalValues({
      ...globalValues,
      followingUsers: updatedFollowingUsers
    });
  };

  return (
    <StateContext.Provider value={{ globalValues, handleClick }}>
      <FollowingSidebar />
    </StateContext.Provider>
  );
}

export default App;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-05
    • 1970-01-01
    • 1970-01-01
    • 2020-06-13
    • 2018-03-17
    • 1970-01-01
    相关资源
    最近更新 更多