Be careful with mapStateToProps, you should only select the part of the store you're interested in, otherwise performance problems could occur
const mapStateToProps = state => ({auth: state.auth});
A little explanation how react-redux connect works,
- each time there is a modification in the store (from the reducers), the
mapStateToPropsfunctions of all the connected components are executed - if the one prop in the returned object is different from the previous one (the operator
===is used) then the component is re-rendered otherwise it does nothing.
In your example, as you select all the props of the store, your component will be re-rendered for each modification in the store
Answer from Olivier Boissé on Stack OverflowReact dispatch going in the infinite loop - javascript
Infinite loop when dispatching redux action
Infinite loop when dispatching redirect from within a componentWillReceiveProps
Getting infinite loop when using React Context Dispatch in useEffect
Hi everyone,
I keep end up getting in an infinite loop whenever my useEffect goes into action
Any help appreciated, I don’t know what more to ask because I’m new to redux
thanks Trigger ⚠️ warning ⚠️ not sure why my code isn’t getting code blocked
`useEffect(()=>{ getUserData();
},[])
My action looks like this:
export function getUserData(){ return async (dispatch) => { try{ let userD = await getUser(); dispatch(setUserD(userD)); }catch(err){ dispatch(setUserErr(err.response)) } }
}`
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) => {
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);
},
},
});
Try adding a condition to compare the props. If your component needs it.
componentWillRecieveProps(nextProps){
if(nextProps.value !== this.props.value)
dispatch(action()) //do dispatch here
}
Your componentWillReceiveProps is in an infinite loop because calling fetchUser will dispatch an action that will update the Props.
Add a comparison to check if the specific prop changes before dispatching the action. EDIT:
In React 16.3+ componentWillReceiveProps will be slowly deprecated.
It is recommended to use
componentDidUpdatein place ofcomponentWillReceiveProps
componentDidUpdate(prevProps) {
if (this.props.params.username !== prevProps.params.username) {
dispatch(fetchUser(username));
}
}
See https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data-when-props-change