come risolvere il ciclo di rendering limite in React
Attualmente sto costruendo un'app di chat utilizzando il database in tempo reale React e Firebase.
ho un problema con il limite massimo di rendering a causa dell'hook usecallback e dello stato degli utenti (gli hook sono addOnConnectLister addOnDisConnectListner).
questo è il codice:
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;
dopo la ricerca ho trovato una soluzione utilizzando useRef per ottenere un riferimento allo stato degli utenti per evitare il problema ma non funziona correttamente la richiamata viene eseguita solo una volta
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;
Risposte
in primo luogo, non sono necessari come stato
const [usersRef] = useState(firebase.database().ref("users"));
const [connectedRef] = useState(firebase.database().ref(".info/connected"));
const [presenceRef] = useState(firebase.database().ref("presence"));
impostarli all'esterno del componente.
const usersRef = firebase.database().ref("users")
const connectedRef = firebase.database().ref(".info/connected")
const presenceRef = firebase.database().ref("presence")
const DirectMessages = (props) => {
......
In secondo luogo, il modo corretto per rimuovere gli ascoltatori sono.
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]);
Cambierò quanto segue
const [users, setUsers] = useState([]);
//users state ref
const getUsersRef = useRef(users);
useEffect(() => {
getUsersRef.current = users;
}, [users]);
per
const [users, _setUsers] = useState([]);
const getUsersRef = useRef(users);
const setUsers = (array) => {
getUsersRef.current = array;
_setUsers(array)
}
infine , gli usecallback non sono necessari, dovresti crearli come funzioni regolari.
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);
}
cambiando questo:
const [users, setUsers] = useState([]);
//users state ref
const getUsersRef = useRef(users);
useEffect(() => {
getUsersRef.current = users;
}, [users]);
a questa:
//users state ref
const getUsersRef = useRef(users);
useEffect(() => {
getUsersRef.current = users;
}, [users]);
dopo aver aggiornato le dipendenze degli hook:
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;