wie man die Limit-Render-Schleife in Reaktion korrigiert
Ich baue gerade eine Chat-App mit React und Firebase Echtzeit-Datenbank.
Ich habe ein Problem mit dem Rendern des maximalen Limits aufgrund des Usecallback-Hooks und des Benutzerstatus (die Hooks sind addOnConnectLister addOnDisConnectListner).
Das ist der Code:
import React, { useEffect, useState, useCallback, useRef } from "react";
import { Icon, Menu } from "semantic-ui-react";
import firebase from "../../firebase";
const DirectMessages = (props) => {
const [usersRef] = useState(firebase.database().ref("users"));
const [connectedRef] = useState(firebase.database().ref(".info/connected"));
const [presenceRef] = useState(firebase.database().ref("presence"));
const [users, setUsers] = useState([]);
//add presence listner
//create a presence record for current user
const addPressenceListner = useCallback(
(currentUserID) => {
connectedRef.on("value", (snap) => {
console.log("connecting snap value", snap.val());
if (snap.val()) {
const ref = presenceRef.child(currentUserID);
ref.set(true);
ref.onDisconnect().remove((err) => {
if (err) console.log(err);
});
}
});
},
[connectedRef, presenceRef]
);
//init connected users
const initUsers = useCallback(
(currentUserID) => {
const loadedUsers = [];
usersRef.on("child_added", (snap) => {
if (currentUserID !== snap.key) {
let user = snap.val();
user["uid"] = snap.key;
user["status"] = "offline";
loadedUsers.push(user);
setUsers([...loadedUsers]);
}
});
},
[usersRef]
);
//update user status when go online
const addOnConnectListner = useCallback(() => {
presenceRef.on("child_added", (snap) => {
const updatedUsers = users.map((user) => {
if (user.uid === snap.key) {
console.log("user connected", user);
user["status"] = "online";
}
return user;
});
setUsers(...[updatedUsers]);
});
}, [presenceRef, users]);
//update user status when go offline
const addOnDisconnectListner = useCallback(() => {
presenceRef.on("child_removed", (snap) => {
const updatedUsers = users.map((user) => {
if (user.uid === snap.key) {
console.log("user disconnected", user);
user["status"] = "offline";
}
return user;
});
setUsers(updatedUsers);
});
}, [users, presenceRef]);
const addListners = useCallback(
(currentUserID) => {
addPressenceListner(currentUserID);
initUsers(currentUserID);
addOnConnectListner();
addOnDisconnectListner();
},
[
addOnConnectListner,
addOnDisconnectListner,
addPressenceListner,
initUsers,
]
);
useEffect(() => {
if (props.currentUser) {
addListners(props.currentUser.uid);
}
return () => {
presenceRef.off();
usersRef.off();
connectedRef.off();
};
}, [props.currentUser, addListners, connectedRef, presenceRef, usersRef]);
const isUserOnline = (user) => user.status === "online";
return (
<Menu.Menu className="menu" style={{ paddingTop: "2em" }}>
<Menu.Item>
<span>
<Icon name="mail" /> Direct Messages
</span>{" "}
({users.length})
</Menu.Item>
{users.map((user) => (
<Menu.Item key={user.uid}>
<Icon name="circle" color={isUserOnline(user) ? "green" : "red"} />@
{user.username}
</Menu.Item>
))}
</Menu.Menu>
);
};
export default DirectMessages;
Nach der Suche fand ich eine Lösung mit useRef, um eine Referenz des Benutzerstatus zu erhalten, um das Problem zu vermeiden, aber nicht richtig funktioniert. Der Rückruf wird nur einmal ausgeführt
import React, { useEffect, useState, useCallback, useRef } from "react";
import { Icon, Menu } from "semantic-ui-react";
import firebase from "../../firebase";
const DirectMessages = (props) => {
const [usersRef] = useState(firebase.database().ref("users"));
const [connectedRef] = useState(firebase.database().ref(".info/connected"));
const [presenceRef] = useState(firebase.database().ref("presence"));
const [users, setUsers] = useState([]);
//users state ref
const getUsersRef = useRef(users);
useEffect(() => {
getUsersRef.current = users;
}, [users]);
//add presence listner
//create a presence record for current user
const addPressenceListner = useCallback(
(currentUserID) => {
connectedRef.on("value", (snap) => {
console.log("connecting snap value", snap.val());
if (snap.val()) {
const ref = presenceRef.child(currentUserID);
ref.set(true);
ref.onDisconnect().remove((err) => {
if (err) console.log(err);
});
}
});
},
[connectedRef, presenceRef]
);
//init connected users
const initUsers = useCallback(
(currentUserID) => {
const loadedUsers = [];
usersRef.on("child_added", (snap) => {
if (currentUserID !== snap.key) {
let user = snap.val();
user["uid"] = snap.key;
user["status"] = "offline";
loadedUsers.push(user);
setUsers([...loadedUsers]);
}
});
},
[usersRef]
);
//update user status when go online
const addOnConnectListner = useCallback(() => {
presenceRef.on("child_added", (snap) => {
const updatedUsers = getUsersRef.current.map((user) => {
if (user.uid === snap.key) {
console.log("user connected", user);
user["status"] = "online";
}
return user;
});
setUsers(...[updatedUsers]);
});
}, [presenceRef, getUsersRef]);
//update user status when go offline
const addOnDisconnectListner = useCallback(() => {
presenceRef.on("child_removed", (snap) => {
const updatedUsers = getUsersRef.current.map((user) => {
if (user.uid === snap.key) {
console.log("user disconnected", user);
user["status"] = "offline";
}
return user;
});
setUsers(updatedUsers);
});
}, [getUsersRef, presenceRef]);
const addListners = useCallback(
(currentUserID) => {
addPressenceListner(currentUserID);
initUsers(currentUserID);
addOnConnectListner();
addOnDisconnectListner();
},
[
addOnConnectListner,
addOnDisconnectListner,
addPressenceListner,
initUsers,
]
);
useEffect(() => {
if (props.currentUser) {
addListners(props.currentUser.uid);
}
return () => {
presenceRef.off();
usersRef.off();
connectedRef.off();
};
}, [props.currentUser, addListners, connectedRef, presenceRef, usersRef]);
const isUserOnline = (user) => user.status === "online";
return (
<Menu.Menu className="menu" style={{ paddingTop: "2em" }}>
<Menu.Item>
<span>
<Icon name="mail" /> Direct Messages
</span>{" "}
({users.length})
</Menu.Item>
{users.map((user) => (
<Menu.Item key={user.uid}>
<Icon name="circle" color={isUserOnline(user) ? "green" : "red"} />@
{user.username}
</Menu.Item>
))}
</Menu.Menu>
);
};
export default DirectMessages;
Antworten
Erstens brauchen Sie sie nicht als Staat
const [usersRef] = useState(firebase.database().ref("users"));
const [connectedRef] = useState(firebase.database().ref(".info/connected"));
const [presenceRef] = useState(firebase.database().ref("presence"));
Stellen Sie sie außerhalb der Komponente ein.
const usersRef = firebase.database().ref("users")
const connectedRef = firebase.database().ref(".info/connected")
const presenceRef = firebase.database().ref("presence")
const DirectMessages = (props) => {
......
Zweitens ist der richtige Weg, um die Listener zu entfernen.
useEffect(() => {
if (props.currentUser) {
addListners(props.currentUser.uid);
return () => {
presenceRef.off('child_added');
presenceRef.off('child_removed');
usersRef.off('child_added');
connectedRef.off('value');
};
}
}, [props.currentUser, addListners, connectedRef, presenceRef, usersRef]);
Ich werde folgendes ändern
const [users, setUsers] = useState([]);
//users state ref
const getUsersRef = useRef(users);
useEffect(() => {
getUsersRef.current = users;
}, [users]);
zu
const [users, _setUsers] = useState([]);
const getUsersRef = useRef(users);
const setUsers = (array) => {
getUsersRef.current = array;
_setUsers(array)
}
schließlich die usecallbacks unnötig sind, sollten Sie sie als reguläre Funktionen erstellen.
const addOnDisconnectListner = () => {
presenceRef.on("child_removed", (snap) => {
const updatedUsers = getUsersRef.current.map((user) => {
if (user.uid === snap.key) {
console.log("user disconnected", user);
user["status"] = "offline";
}
return user;
});
setUsers(updatedUsers);
}
dies ändern:
const [users, setUsers] = useState([]);
//users state ref
const getUsersRef = useRef(users);
useEffect(() => {
getUsersRef.current = users;
}, [users]);
dazu:
//users state ref
const getUsersRef = useRef(users);
useEffect(() => {
getUsersRef.current = users;
}, [users]);
nach dem Aktualisieren der Hooks-Abhängigkeiten:
import React, { useEffect, useState, useCallback, useRef } from "react";
import { Icon, Menu } from "semantic-ui-react";
import firebase from "../../firebase";
const usersRef = firebase.database().ref("users");
const connectedRef = firebase.database().ref(".info/connected");
const presenceRef = firebase.database().ref("presence");
const DirectMessages = (props) => {
const [users, _setUsers] = useState([]);
//get users ref
const getUsersRef = useRef(users);
const setUsers = (array) => {
getUsersRef.current = array;
_setUsers(array);
};
//add presence listner
//create a presence record for current user
const addPressenceListner = useCallback((currentUserID) => {
connectedRef.on("value",(snap) => {
console.log("connecting snap value", snap.val());
if (snap.val()) {
const ref = presenceRef.child(currentUserID);
ref.set(true);
ref.onDisconnect().remove((err) => {
if (err) console.log(err);
});
}
});
}, []);
//init connected users
const initUsers = useCallback((currentUserID) => {
const loadedUsers = [];
usersRef.on("child_added", (snap) => {
if (currentUserID !== snap.key) {
let user = snap.val();
user["uid"] = snap.key;
user["status"] = "offline";
loadedUsers.push(user);
setUsers([...loadedUsers]);
}
});
}, []);
//update user status when go online
const addOnConnectListner = useCallback(() => {
presenceRef.on("child_added", (snap) => {
const updatedUsers = getUsersRef.current.map((user) => {
if (user.uid === snap.key) {
user["status"] = "online";
}
console.log("user", user);
return user;
});
setUsers(...[updatedUsers]);
});
}, []);
//update user status when go offline
const addOnDisconnectListner = useCallback(() => {
presenceRef.on("child_removed", (snap) => {
const updatedUsers = getUsersRef.current.map((user) => {
if (user.uid === snap.key) {
console.log("user disconnected", user);
user["status"] = "offline";
}
return user;
});
setUsers(updatedUsers);
});
}, [getUsersRef]);
const addListners = useCallback(
(currentUserID) => {
addPressenceListner(currentUserID);
initUsers(currentUserID);
addOnConnectListner();
addOnDisconnectListner();
},
[
addOnConnectListner,
addOnDisconnectListner,
addPressenceListner,
initUsers,
]
);
useEffect(() => {
if (props.currentUser) {
addListners(props.currentUser.uid);
}
return () => {
presenceRef.off();
usersRef.off();
connectedRef.off();
};
}, [props.currentUser, addListners]);
const isUserOnline = (user) => user.status === "online";
return (
<Menu.Menu className="menu" style={{ paddingTop: "2em" }}>
<Menu.Item>
<span>
<Icon name="mail" /> Direct Messages
</span>{" "}
({users.length})
</Menu.Item>
{users.map((user) => (
<Menu.Item key={user.uid}>
<Icon name="circle" color={isUserOnline(user) ? "green" : "red"} />@
{user.username}
</Menu.Item>
))}
</Menu.Menu>
);
};
export default DirectMessages;