Infinite Loop using React, Redux and Firebase - reactjs

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

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

Related

TypeError: Cannot read properties of undefined (reading 'todos')

When I try to run this code it gives me this error:
TypeError: Cannot read properties of undefined (reading 'todos')
how to fix it
I wanted to make a todo list that will work after the user registers, but todo does not work after registration
My code:
HomePage.js
const HomePage = () => {
const dispatch = useDispatch();
const { isAuth, email } = useAuth();
return isAuth ? (
<div>
<TodoForm />
<button onClick={() => dispatch(removeUser())}>
Log out from {email}
</button>
</div>
) : (
<Redirect to="/login" />
);
};
TodoForm.js
const TodoForm = () => {
const todos = useSelector((state) => state.todo.todos)
// const todos = useSelector((state) => state.todo.todos);
const dispatch = useDispatch();
const [todoValue, setTodoValue] = useState("");
const addTodoHandler = (e) => {
e.preventDefault();
const todo = {
id: v4(),
text: todoValue,
completed: false,
};
dispatch(addTodo(todo));
setTodoValue("");
};
const handleChange = (e) => {
setTodoValue(e.target.value);
};
console.log(todos);
return (
<>
<form onSubmit={addTodoHandler}>
<input
type="text"
value={todoValue}
onChange={handleChange}
placeholder="Add task"
/>
<button type="submit">Submit</button>
</form>
{todos.map((todo) => (
<TodoList key={todo.id} todo={todo} />
))}
</>
);
};
todoSlice.js
const initialState = {
todos: [],
};
export const todoSlice = createSlice({
name: "todos",
initialState,
reducers: {
addTodo: (state, action) => {
state.todos.push(action.payload);
},
removeTodo: (state, action) => {
state.todos = state.todos.filter((todo) => todo.id !== action.payload);
},
completedTodo: (state, action) => {
const toggleTodo = state.todos.find((todo) => todo.id === action.payload);
toggleTodo.completed = !toggleTodo.completed;
},
},
});
store
reducer: {
todo: todoSlice,
user: userReducer,
},
});
help me fix this, I will be very grateful
slicename is todos not todo
const todos = useSelector((state) => state.todos.todos)

having problem with use dispatch in redux

I try to get firestore database and dispatch this database in redux. and print this database in my listpage.
I succeed to get firestore database, and console.log are print all data. but I try to use map() function. print only one data. I don't know why this had happened.
I think my code is wrong. but I don't know where I got it wrong.
DictCard.js
import { collection, getDocs} from "firebase/firestore";
import React, { useEffect } from "react";
import { db } from "../firebase";
import { useSelector, useDispatch } from "react-redux";
import { getDict } from "../redux/ListReducer";
const Card = ({dict}) => {
return(
<div className="inbox">
<p className="text1">단어</p>
<p className="text2">{dict.word}</p>
<p className="text1">설명</p>
<p className="text2">{dict.explain}</p>
<p className="text1">예시</p>
<p className="text2" style={{color:"lightskyblue",paddingBottom:"0"}}>{dict.example}</p>
</div>
)
}
const DictCard = () => {
const dictList = useSelector((state) => state.dictList.dicts);
const dispatch = useDispatch();
useEffect( async () => {
const query = await getDocs(collection(db, "dict"));
query.forEach(doc => {
console.log([doc.id, doc.data()])
dispatch(getDict([{id: doc.id, text: doc.data()}]))
});
},[]);
return(
<div className="dict-card" >
{dictList.map((dict) => (
<Card dict = {dict.text} key = {dict.id}/>
))}
</div>
)
}
export default DictCard;
ListReducer.js
import { createSlice } from "#reduxjs/toolkit";
// const initialState = [{id:"",text:{word:"",explain:"",example:""}}]
const initState = {
dicts: [{
id:"",
text:{
word:"",
explain:"",
example:""
}
},]
}
export const ListReducer = createSlice({
name: "dictList",
initialState: initState,
reducers: {
addDict: (state, action) => {
state.dicts = action.payload
},
getDict: (state, action) => {
state.dicts = action.payload
},
updateDict: (state, action) => {
},
deleteDict: (state, action) => {
},
},
});
export const { addDict, getDict, updateDict, deleteDict } = ListReducer.actions;
export default ListReducer.reducer;
I think dispatch's position is wrong but i have no idea
I solved problem.
useEffect( async () => {
const arr = []
const query = await getDocs(collection(db, "dict"));
query.forEach(doc => {
console.log([doc.id, doc.data()])
// dispatch(getDict([{id: doc.id, text: doc.data()}]))
arr.push({id: doc.id, text: doc.data()})
});
dispatch(getDict(arr))
},[]);
need to make empty array
Try not to use async function for useEffect
useEffect(() => {
const fetchData = async () => {
const query = await getDocs(collection(db, "dict"));
query.forEach(doc => {
console.log([doc.id, doc.data()])
dispatch(getDict([{id: doc.id, text: doc.data()}]))
});
}
fetchData()
},[]);
I think the issue may caused from the useEffect function. If it not, please comment below so i can track the issue more clearly

I need to rerender functional component

I have a component
const MovieDetail = (props) => {
//const [state, setstate] = useState(initialState)
const { id } = useParams();
const movie = useSelector(getMovie)
const movies = useSelector(getAllMovies)
const dispatch = useDispatch();
useEffect(() => {
fetchMovieById(id)
.then((response) => dispatch(addMovie(response)))
}, [movies])
const onSubmit = (fromData) => {
//here
dispatch(addComments({fromData, id, movies}));
console.log(fromData);
}
return (
<div className='container'>
<br/>
{
<div className='detail'>
<div>
<h2>{movie.title}</h2>
<div>
<img src={movie.img} alt={movie.title}/>
</div>
</div>
<div className='description'>
<div>
{movie.details}
</div>
<div className='comments'>
<h5>Stay your comment here</h5>
<CommentReduxForm onSubmit={onSubmit}/>
</div>
<div className='comments'>
{movie.comments ? movie.comments : <div>no comments yet</div>}
</div>
<div className='ratio'>
<strong>Ratio: {movie.ratio}</strong>
</div>
</div>
</div>
}
</div>
)
}
export default MovieDetail
I'm using redux toolkit, I'm trying to add comment to movie. I come on the site and select the movie, in the form I put my comment, then I click bottun to add comment, then I go to devtools in my browser and I see My comment in the state, but on the page, I see "there are not comments", so I have updated state, but not updated page, and i think I need to rerender my component, or maybe I need another technic?
Also there is my slice with actions and reducers, help me please if anybody can
const initialState = {
movies: [],
movie: {}
}
const movieSlice = createSlice({
name: "movies",
initialState,
reducers: {
addMovies: (state, {payload}) => {
state.movies = payload;
},
addMovie: (state, action) => {
state.movie = action.payload[0];
},
addComments: (state, action) => {
debugger
let author = action.payload.fromData.yourName;
let comment = action.payload.fromData.yourComment;
let movieId = action.payload.id;
let moviesArr = action.payload.movies;
let obj = moviesArr.find((item) => item.id == movieId);
let newObj = {...obj, comments: comment}
const newArr = moviesArr.map(o => {
if (o.id === newObj.id) {
return newObj;
}
return o;
})
state.movies = newArr;
}
},
});
export const {addMovies, addMovie, addComments} = movieSlice.actions;
export const getAllMovies = (state) => state.movies.movies;
export const getMovie = (state) => state.movies.movie;
export default movieSlice.reducer;
In Redux, reducers are not allowed to mutate the original / current state values
You can see these rules in reducer rules
So you should return an updated state instead of changing the oringials
const initialState = {
movies: [],
movie: {}
}
const movieSlice = createSlice({
name: "movies",
initialState,
reducers: {
addMovies: (state, {payload}) => {
return {movies:payload,...state}
},
addMovie: (state, action) => {
return {movie:action.payload[0],...state}
},
addComments: (state, action) => {
let author = action.payload.fromData.yourName;
let comment = action.payload.fromData.yourComment;
let movieId = action.payload.id;
let moviesArr = action.payload.movies;
let obj = moviesArr.find((item) => item.id == movieId);
let newObj = {...obj, comments: comment}
const newArr = moviesArr.map(o => {
if (o.id === newObj.id) {
return newObj;
}
return o;
})
return {movies:newArr,...state}
}
},
});
export const {addMovies, addMovie, addComments} = movieSlice.actions;
export const getAllMovies = (state) => state.movies.movies;
export const getMovie = (state) => state.movies.movie;
export default movieSlice.reducer;

how to prevent re-render react-redux

In the categories component, I render a random image from each category. I also added a onClick event to each image. When the image is clicked, it will dispatch the action getCategory(target.alt) and the DOM will render the products from the clicked category. The problem I got is that every time I clicked a random category image, the DOM will re-render and new random images will appear on the DOM. How do I prevent this re-render? Below is my codes.
const Categories = ({selectedCategory}) => {
const isLoading = useSelector(state => state.productsReducer.isLoading);
const productsByCategory = useSelector(state =>
state.productsReducer.productsByCategories);
const getRandomProductsByCategory = () => {
const randomProducts = []
for(let categories in productsByCategory) {
const randomCategory = productsByCategory[categories][getRandomIndex(productsByCategory[categories].length)];
productsByCategory[categories].map(category => {
if(category === randomCategory) {
randomProducts.push(category)
}
})
}
return randomProducts;
}
return (
<div class='categories-container'>
{getRandomProductsByCategory().map(randomProduct => (
<img onClick={selectedCategory} src={randomProduct.image} />}
</div>
)
}
function App() {
const dispatch = useDispatch();
const category = useSelector(state => state.productsReducer.category)
useEffect(() => {
dispatch(getProducts())
}, [dispatch])
const handleCategoryClick = ({target}) => {
return dispatch(getCategory(target.alt))
}
return (
<>
{/* <ProductsList /> */}
<Categories selectedCategory={handleCategoryClick} />
{category.map(product => <img src={product.image} />)}
</>
)
}
const populateProductsStarted = () => ({
type: 'POPULATE_PRODUCTS/fetchStarted'
})
const populateProductsSuccess = products => ({
type: 'POPULATE_PRODUCTS/fetchSuccess',
payload: products
})
const populateProductsFailed = error => ({
type: 'POPULATE_PRODUCTS/fetchFailed',
error
})
export const getCategory = (category) => ({
type: 'GET_CATEGORY',
category
})
const getProducts = () => async dispatch => {
dispatch(populateProductsStarted())
try {
const response = await fetch(url)
if(response.ok) {
let jsonResponse = await response.json();
return dispatch(populateProductsSuccess(jsonResponse))
}
} catch (err) {
dispatch(populateProductsFailed(err.toString()))
}
}
const initialState = {
isLoading: false,
isError: null,
allProducts: [],
productsByCategories: {},
category: []
}
const productsReducer = (state=initialState, action) => {
switch(action.type) {
case 'POPULATE_PRODUCTS/fetchStarted':
return {
...state,
isLoading: true
}
case 'POPULATE_PRODUCTS/fetchSuccess':
return {
...state,
isLoading: false,
allProducts: action.payload,
productsByCategories: action.payload.reduce((accumulatedProduct, currentProduct) => {
accumulatedProduct[currentProduct.category] = accumulatedProduct[currentProduct.category] || [];
accumulatedProduct[currentProduct.category].push(currentProduct);
return accumulatedProduct;
}, {})
}
case 'POPULATE_PRODUCTS/fetchFailed':
return {
...state,
isError: action.error
}
case 'GET_CATEGORY':
return {
...state,
category: state.allProducts.filter(product => product.category === action.category)
}
default:
return state
}
}
One way to achieve this is through memoization provided by React's useMemo.
const images = React.useMemo(getRandomProductsByCategory().map(randomProduct => (
<img onClick={selectedCategory} src={randomProduct.image} />, [productsByCategory])
return (
<div class='categories-container'>
{images}
</div>
)
This will keep the srcs consistent across re-renders.

React-Redux Functional Component Multiple Renders

I created a very simple React-Redux App and fetching Users and Posts from https://jsonplaceholder.typicode.com/
In my components I am logging Users and Posts data into the console. As far as I see, in the network tab there is one request for Users and 10 requests for Posts. That's correct but in the console, I see 10 Posts requests for each User.
Does it mean ReactJS renders the component 100 times? What is my mistake in this code?
Any help will be greatly appreciated!
My code and codepen link are below
Please check the code in codepen
const { useEffect } = React;
const { connect, Provider } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;
//-- REDUCERS START -- //
const userReducer = (state = [], action) => {
if (action.type === 'fetch_users') return [...action.payload];
return state;
};
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...action.payload];
return state;
};
//-- REDUCERS END -- //
//-- ACTIONS START -- //
const fetchUsers = () => async dispatch => {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'fetch_users', payload: response.data });
};
const fetchPosts = userId => async dispatch => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${userId}/posts`
);
dispatch({ type: 'fetch_posts', payload: response.data });
};
//-- ACTIONS END -- //
const reducer = combineReducers({ users: userReducer, posts: postReducer });
const store = createStore(reducer, applyMiddleware(thunk));
const mapStateToProps = state => {
return { users: state.users, posts: state.posts };
};
const mapDispatchToProps = dispatch => {
return {
getUsers: () => dispatch(fetchUsers()),
getPosts: (id) => dispatch(fetchPosts(id))
};
};
const Users = props => {
console.log('users', props.users);
const { getUsers } = props;
useEffect(() => {
getUsers();
}, [getUsers]);
const renderUsers = () =>
props.users.map(user => {
return (
<div>
<div>{user.name}</div>
<div>
<PostsContainer userId={user.id} />
</div>
</div>
);
});
return <div style={{backgroundColor:'green'}}>{renderUsers()}</div>;
};
const UserContainer = connect(mapStateToProps, mapDispatchToProps)(Users);
const Posts = props => {
console.log('posts' , props.posts);
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [getPosts, userId]);
const renderPosts = () =>
props.posts.map(post => {
return (
<div>
<div>{post.title}</div>
</div>
);
});
return <div style={{backgroundColor:'yellow'}}>{renderPosts()}</div>;
};
const PostsContainer = connect(mapStateToProps, mapDispatchToProps)(Posts);
const App = props => {
return (
<div>
<UserContainer />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
Does it mean ReactJS renders the component 100 times? What is my mistake in this code?
you have a UserContainer, that renders and requests for users;
once fetched users, you have an update state. UserContainer rerenders, and now you have 10 PostContainers;
each PostContainer makes a request to fetch posts, 10 on total;
it results in 10 state updates. UserContainer rerenders 10 times, and each PostContainer rerenders 10 times;
The component doesn't renders 100 times, each PostContainer renders the initial mount then rerenders 10 times. since there are 10 PostContainers and each rerenders 10 times that's why you might think that renders 100 times.
you have some issues. the dependency issue, which was pointed out is the first. getUsers useEffect should have an empty dependency, and userId useEffect, should depend on userId.
to solve the 10 rerenders on UserContainer due to posts, you need to have a different mapStateToProps to each. for UserContainer you will map only users, otherwise you will get 10 updates due to 10 posts requests:
const mapUserStateToProps = state => {
return { users: state.users };
};
with that it solves UserContainer 10 rerenders.
now about PostContainer there is something that needs to be fixed first, your reducer. your reducer replaces last posts with the current call. in the end you will have only the posts that arrived last, not all posts. to fix that you need to spread your state.
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...state, ...action.payload];
return state;
};
eventually, if in your project you could have a repeated request to same userId than it would be necessary to have some validation for not adding the same posts again
now it leads us to mapping props to PostContainer. you would need to have a filter on posts based on userId. mapStateToProps takes props as second argument, which enables us to accomplish that:
const mapPostStateToProps = (state, { userId }) => {
return { posts: state.posts.filter(post => post.userId === userId) };
};
this looks the end to solve the issue, but each PostContainer still rerenders 10 times. why does this happens since posts will be the same? that happens because filter will return a new array reference, no matter if its content didn't change.
to solve this issue you can use React.memo. you need to provide the component and a equality function to memo. to compare an array of objects there are some solutions, also few libs that provide some deepEqual function. here I use JSON.stringify to compare, but you are free to use some other one:
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.posts) === JSON.stringify(nextProps.posts)
}
you would validate also other props that could change but that's not the case
now apply React.memo to posts:
const PostsContainer = connect(mapPostStateToProps, mapDispatchToProps)(React.memo(Posts, areEqual));
After all that applied, UserContainer will rerender one once, and each PostContainer will rerender only once as well.
here follows link with working solution:
https://codepen.io/rbuzatto/pen/BaLYmNK?editors=0010
final code:
const { useEffect } = React;
const { connect, Provider } = ReactRedux;
const { createStore, applyMiddleware, combineReducers } = Redux;
const thunk = ReduxThunk.default;
//-- REDUCERS START -- //
const userReducer = (state = [], action) => {
if (action.type === 'fetch_users') return [...action.payload];
return state;
};
const postReducer = (state = [], action) => {
if (action.type === 'fetch_posts') return [...state, ...action.payload];
return state;
};
//-- REDUCERS END -- //
//-- ACTIONS START -- //
const fetchUsers = () => async dispatch => {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'fetch_users', payload: response.data });
};
const fetchPosts = userId => async dispatch => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${userId}/posts`
);
dispatch({ type: 'fetch_posts', payload: response.data });
};
//-- ACTIONS END -- //
const reducer = combineReducers({ users: userReducer, posts: postReducer });
const store = createStore(reducer, applyMiddleware(thunk));
const mapUserStateToProps = state => {
return { users: state.users };
};
const mapPostStateToProps = (state, { userId }) => {
return { posts: state.posts.filter(post => post.userId === userId) };
};
const mapDispatchToProps = dispatch => {
return {
getUsers: () => dispatch(fetchUsers()),
getPosts: (id) => dispatch(fetchPosts(id))
};
};
const Users = props => {
console.log('users', props.users);
const { getUsers } = props;
useEffect(() => {
getUsers();
}, []);
const renderUsers = () =>
props.users.map(user => {
return (
<div key={user.id}>
<div>{user.name}</div>
<div>
<PostsContainer userId={user.id} />
</div>
</div>
);
});
return <div style={{backgroundColor:'green'}}>{renderUsers()}</div>;
};
const UserContainer = connect(mapUserStateToProps, mapDispatchToProps)(Users);
const Posts = props => {
console.log('posts');
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [userId]);
const renderPosts = () =>
props.posts.map(post => {
return (
<div>
<div>{post.title}</div>
</div>
);
});
return <div style={{backgroundColor:'yellow'}}>{renderPosts()}</div>;
};
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps.posts) === JSON.stringify(nextProps.posts)
}
const PostsContainer = connect(mapPostStateToProps, mapDispatchToProps)(React.memo(Posts, areEqual));
const App = props => {
return (
<div>
<UserContainer />
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
useEffect() renders the component every time something is changed in the dependencies you provided.
Ideally, you should change your components to re-render only when something changes in props. getUser and getPost change on each render. So, it is better to change it to monitor users and posts from state.
In Users:
const { users, getUsers } = props;
useEffect(() => {
getUsers();
}, []); -- Leaving this empty makes it load only on mount.
In Posts:
const { getPosts, userId } = props;
useEffect(() => {
getPosts(userId);
}, [userId]);

Resources