comment corriger la boucle de rendu de limite dans react

Nov 28 2020

Je suis en train de créer une application de chat en utilisant la base de données en temps réel React et Firebase.

J'ai un problème avec le rendu de limite maximale en raison du hook usecallback et de l'état des utilisateurs (les hooks sont addOnConnectLister addOnDisConnectListner).

c'est le 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;

après la recherche, j'ai trouvé une solution en utilisant useRef pour obtenir une référence de l'état des utilisateurs afin d'éviter le problème mais ne fonctionne pas correctement, le rappel ne s'exécute qu'une seule fois

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;

Réponses

SomeoneSpecial Nov 29 2020 at 13:08

tout d'abord, vous n'en avez pas besoin comme état

  const [usersRef] = useState(firebase.database().ref("users"));
  const [connectedRef] = useState(firebase.database().ref(".info/connected"));
  const [presenceRef] = useState(firebase.database().ref("presence"));

placez-les en dehors du composant.

const usersRef = firebase.database().ref("users")
const connectedRef = firebase.database().ref(".info/connected")
const presenceRef = firebase.database().ref("presence")

const DirectMessages = (props) => {
......

Deuxièmement, la bonne façon de supprimer les auditeurs est.

 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]);

Je vais changer ce qui suit

  const [users, setUsers] = useState([]);

  //users state ref
  const getUsersRef = useRef(users);

  useEffect(() => {
    getUsersRef.current = users;
  }, [users]);

à

  const [users, _setUsers] = useState([]);
  const getUsersRef = useRef(users);

  const setUsers = (array) => {
        getUsersRef.current = array;
        _setUsers(array)
  }

enfin , les usecallbacks sont inutiles, vous devez les créer comme des fonctions régulières.

  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);
    }
B.Fateh Nov 29 2020 at 14:51

changer ceci:

  const [users, setUsers] = useState([]);

  //users state ref
  const getUsersRef = useRef(users);

  useEffect(() => {
    getUsersRef.current = users;
  }, [users]);

pour ça:

  //users state ref
  const getUsersRef = useRef(users);

  useEffect(() => {
    getUsersRef.current = users;
  }, [users]);

après la mise à jour des dépendances de hooks:

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;