skip to Main Content

I have a collection of profile documents in firebase and I want to render them in the profiles page, however after I have updated the userProfiles state and use useDispatch to store the state in the slice, I get an infinite loop when rendering the profile page.

I have tried putting the dispatch() into a useEffect, not in a useEffect and inside the querySnapshot promise but I’m still getting an infinite loop wherever I put it.

Any feedback is appreciated, thank you.

\ profiles.js

export const Profiles = () => {
  const [userProfiles, setUserProfiles] = useState([]);
  const dispatch = useDispatch();

  const navigate = useNavigate();
  const user = useSelector(selectUser);

  db.collection("customers")
    .doc(user.info.uid)
    .collection("profiles")
    .get()
    .then((querySnapshot) => {
      const documents = querySnapshot.docs.map((doc) => doc.data());
      setUserProfiles(documents);
    });

  useEffect(() => {
    dispatch(profiles(userProfiles));
  }, []);

  console.log({ userProfiles });

  return (
    <div className="profile_container">
      <h1 className="profile_title">Who's Watching?</h1>
      <div className="profile_row">
        {userProfiles.map((profile) => {
          return (
            <div className="profile_individualProfile">
              <img
                src="https://occ-0-300-1167.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABY5cwIbM7shRfcXmfQg98cqMqiZZ8sReZnj4y_keCAHeXmG_SoqLD8SXYistPtesdqIjcsGE-tHO8RR92n7NyxZpqcFS80YfbRFz.png?r=229"
                alt="profile"
              />
              <p>{profile.name}</p>
            </div>
          );
        })}
        <div
          onClick={() => navigate("/add-profile")}
          className="profile_addProfile_container"
        >
          <img
            src="https://img.icons8.com/ios-glyphs/30/FFFFFF/plus--v1.png"
            alt="add profile"
          />
          <h2>Add Profile</h2>
        </div>
      </div>
    </div>
  );
};
\ userSlice.js

export const userSlice = createSlice({
  name: "user",
  initialState: {
    user: {
      info: null,
      profiles: [],
    },
  },
  reducers: {
    login: (state, action) => {
      state.user.info = action.payload;
    },
    logout: (state) => {
      state.user.info = null;
    },
    profiles: (state, action) => {
      state.user.profiles.push(action.payload);
      },
  },
});

2

Answers


  1. Chosen as BEST ANSWER

    For anyone that runs into this issue you may find this useful. Following from yilmaz's helpful answer, I had to update the Profiles.js and userSlice.js as follows...

    // Profiles.js
    
    export const Profiles = () => {
      const dispatch = useDispatch();
      const navigate = useNavigate();
      const usersState = useSelector(profiles);
    
      useEffect(() => {
        db.collection("customers")
          .doc(usersState.payload.user.user.info.uid)
          .collection("profiles")
          .get()
          .then((querySnapshot) => {
            const documents = querySnapshot.docs.map((doc) => doc.data());
            !usersState.payload.user.user.profiles.includes((arr) =>
              documents.every(arr)
            ) && dispatch(profiles(documents));
          });
      }, []);
    
      return (
        <div className="profile_container">
          <h1 className="profile_title">Who's Watching?</h1>
          <div className="profile_row">
            {usersState.payload.user.user.profiles.map((profile) => {
              console.log(profile);
    
              return (
                <div className="profile_individualProfile">
                  <img
                    src="https://occ-0-300-1167.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABY5cwIbM7shRfcXmfQg98cqMqiZZ8sReZnj4y_keCAHeXmG_SoqLD8SXYistPtesdqIjcsGE-tHO8RR92n7NyxZpqcFS80YfbRFz.png?r=229"
                    alt="profile"
                  />
                  <p>{profile.name}</p>
                </div>
              );
            })}
            <div
              onClick={() => navigate("/add-profile")}
              className="profile_addProfile_container"
            >
              <img
                src="https://img.icons8.com/ios-glyphs/30/FFFFFF/plus--v1.png"
                alt="add profile"
              />
              <h2>Add Profile</h2>
            </div>
          </div>
        </div>
      );
    };
    
    // userSlice.js
    
    export const userSlice = createSlice({
      name: "user",
      initialState: {
        user: {
          info: null,
          profiles: [],
        },
      },
      reducers: {
        login: (state, action) => {
          state.user.info = action.payload;
        },
        logout: (state) => {
          state.user.info = null;
        },
        profiles: (state, action) => {
          state.user.profiles.length = 0;
    
          state.user.profiles.push(...action.payload);
        },
      },
    });
    

  2. In the current implementation, when your page is rendered, db.collections runs and you set state setUserProfiles(documents) which renders your app and again db.collections runs. to prevent this you should run db.collections in useEffect.

    // fetch users only when your app renders
    useEffect(() => {
        db.collection("customers")
          .doc(user.info.uid)
          .collection("profiles")
          .get()
          .then((querySnapshot) => {
            const documents = querySnapshot.docs.map((doc) => doc.data());
            setUserProfiles(documents);
          });
      }, []);
    

    have another useEffect

    useEffect(() => {
        dispatch(profiles(userProfiles));
      }, [userProfiles]);
    

    this will NOT work neither. setUserProfiles will be causing issue. Because when app renders, you fetch data, you set the state, change the userProfiles, this will rerender app again.

    The problem with your code is you do not need setUserProfiles. instead in db.collections() when you get the documents, you dispatch the documents and then access the profiles from redux with useSelector

    // fetch users only when your app renders
    useEffect(() => {
        db.collection("customers")
          .doc(user.info.uid)
          .collection("profiles")
          .get()
          .then((querySnapshot) => {
            const documents = querySnapshot.docs.map((doc) => doc.data());
            // setUserProfiles(documents); You do not need this
            dispatch(profiles(userProfiles))
          });
      }, []);
    

    Now use useSelector to reach the state in redux

    // assuming reducers name is "users"
      const usersState = useSelector((state) => state.users);
    

    now when you use map guard your app

     // make sure you use the correct data
     // you migh need to destructure
     {usersState && usersState.map((profile) => {
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search