#javascript #reactjs #react-hooks #use-effect #use-state
#javascript #reactjs #реагирующие крючки #использование-эффект #состояние использования
Вопрос:
В моем Main.js Я создаю первое глобальное состояние с именем пользователя и списком пользователей, за которыми я слежу.
Затем как компонент Wall, так и FollowingSidebar отображают список подписчиков и их сообщения (плюс сообщения основного пользователя).
Пока все хорошо. Но во вложенном компоненте внутри FollowingSidebar, называемом FollowingUser, у меня есть onClick для удаления пользователя. Я понимаю, что, поскольку я изменяю состояние, useEffect позаботится о компоненте стены, чтобы повторно отобразить его, но ничего не происходит… Я проверил несколько примеров в Интернете, но пока ничто не помогло моему варианту использования.
Излишне говорить, что я не слишком разбираюсь в React, и хуки немного сложны.
Код здесь:
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;
Комментарии:
1. Как вы реализовали свой StateContext?
2. @ImranRafiqRather Я добавил это в конце основного сообщения.
3. Хм. Да, я проверил.. Позвольте мне еще раз проверить код 🙂 Я надеюсь, что все данные поступают из firestore правильно…
4. Да, это так. Сейчас я вижу, что setFollows в FollowingUser по какой-то причине фактически не обновляет состояние.
5. Я создал аналогичную демо-версию 🙂 Просто дай мне минутку, чтобы посмотреть, что происходит, приятель 🙂
Ответ №1:
Основная проблема заключается в настройке state
переменных в Main.js
файле (эти данные фактически должны быть частью состояния Context
для обработки глобально).
Приведенный ниже код не обновит наше состояние глобально.
const globalValues = {
mainUserId: mainUser,
followingUsers: follows
};
Мы должны записать состояние таким образом, чтобы оно изменялось на уровне глобального контекста.
Итак, в пределах вашего Main.js
установленного состояния, как показано ниже:
const [globalValues, setGlobalValues] = useState({
mainUserId: "uid_MainUser",
followingUsers: []
});
Также добавьте все свои event handlers
Context Level
in Main.js
только для того, чтобы избежать 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;