简体   繁体   中英

how to fix limit render loop in react

i am currently building a chat app using react and Firebase real time database.

i have a problem with maximum limit rendering because of usecallback hook and users state (the hooks are addOnConnectLister addOnDisConnectListner).

this is the 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;

after searching i found a solution using useRef to get a reference of users state to avoid the problem but is not working correctly the callback is runnging only once

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;

firstly, you do not need them as state

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

set them outside the component.

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

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

Secondly, the correct way to remove the listeners are.

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

I will change the following

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

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

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

to

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

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

lastly , the usecallbacks are unnecessary, you should create them as regular functions.

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

changing this:

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

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

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

to this:

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

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

after updating hooks dependencies:

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM