getting callstack with react hooks and infinite scroll? - reactjs

i have already tried useMemo and useEffect, but i can't seem to figure out why my code don't work:
const App: React.FC = () => {
const dispatch = useDispatch();
const [page, setPage] = useState(1);
const { userIds, users, totalUsers } = useSelector(
({ users }: RootState) => users
);
const renderUsers = useMemo(() => {
return userIds.map(userId => (
<div key={users[userId].first_name}>{users[userId].first_name}</div>
));
}, [userIds, users]);
const hasMore = useMemo(() => {
return userIds.map(userId => userId).length < totalUsers;
}, [userIds, totalUsers]);
const fetchUsers = useCallback(
async (page: number) => {
dispatch({
type: FETCH_USERS_REQUEST,
payload: { page }
});
try {
const { data, ...result } = await api.fetchUsers(page);
const user = new schema.Entity('users');
const {
entities,
result: { users: userIds }
} = normalize({ users: data }, { users: [user] });
dispatch({
type: FETCH_USERS_SUCCESS,
payload: {
...result,
users: entities.users,
userIds
}
});
} catch (error) {
dispatch({ type: FETCH_USERS_FAILURE, payload: { error } });
}
},
[dispatch]
);
useEffect(() => {
fetchUsers(1);
}, [fetchUsers]);
let scrollParentRef: HTMLDivElement | null = null;
return (
<div className="vh-100 vw-100">
<Header />
<div
className="container overflow-auto"
ref={div => {
scrollParentRef = div;
}}
>
<InfiniteScroll
pageStart={0}
loadMore={async page => await fetchUsers(page)}
hasMore={hasMore}
loader={
<div className="loader" key={0}>
Loading ...
</div>
}
useWindow={false}
getScrollParent={() => scrollParentRef}
>
{renderUsers}
</InfiniteScroll>
</div>
</div>
);
};
I am using infinite scroll and the error i got is call stack, please help me fix this?

Related

React limits the number of renders to prevent an infinite loop...Too many re-renders

How would I avoid the infinite loop issue?
I'm getting an error while rendering the following component:
Too many re-renders. React limits the number of renders to prevent an infinite loop.?
TeamContent.js re-renders multiple times, how can I set an initial render on load?
Error given
TeamContent.js
import { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
fetchTeamPlayers,
fetchUpcomingGames,
fetchPreviousGames,
fetchLiveGames,
} from "../../../data/UserInfo/infoActions";
import TeamPlayers from "./TeamPlayers";
import TeamNext from "./TeamNext";
import TeamPrevious from "./TeamPrevious";
import LiveEvent from "./Live.js/LiveEvent";
function TeamContent(props) {
console.log("test");
let containsLiveGame = false;
const dispatch = useDispatch();
const liveGames = useSelector((store) => store.userInfo.live.games.all);
const status = useSelector((store) => store.userInfo.playersLoadStatus);
const UpcomingGamesstatus = useSelector(
(store) => store.userInfo.upcomingGamesStatus
);
const previousGamesStatus = useSelector(
(store) => store.userInfo.previousGamesStatus
);
const liveStatus = useSelector((store) => store.userInfo.live.games.status);
liveGames.map((game) => {
const verifyHomeTeam = +game.idHomeTeam === +props.teamID;
const verifyAwayTeam = +game.idAwayTeam === +props.teamID;
if (verifyAwayTeam || verifyHomeTeam) {
containsLiveGame = true;
}
});
// -----> request team data
useEffect(() => {
dispatch(fetchTeamPlayers(props.teamID));
dispatch(fetchUpcomingGames(props.teamID));
dispatch(fetchPreviousGames(props.teamID));
dispatch(fetchLiveGames());
}, [dispatch, props.teamID]);
useEffect(() => {
dispatch(fetchLiveGames());
const interval = setInterval(() => {
dispatch(fetchLiveGames());
}, 30000);
return () => clearInterval(interval);
}, [dispatch]);
return (
<div className="teamDash">
<div className="dashLeft">
<div
className="dashLeftHead"
style={{
backgroundImage: `url(${props.stadiumImg})`,
}}
>
<div className="dashLeftHeadAbs"></div>
<div className="dashLeftHeadIntro">
<span>{props.stadiumName}</span>
<h3>{props.teamName}</h3>
</div>
</div>
{liveStatus !== "error" && containsLiveGame && <LiveEvent />}
{status !== "error" && (
<div className="dashLeftPlayers">
<TeamPlayers />
</div>
)}
<div className="dashLeftDesc">
<p>{props.teamDesc}</p>
</div>
</div>
<div className="dashRight">
{UpcomingGamesstatus === "error" ? (
console.log("unable to load upcoming games")
) : (
<div className="upcomingGames">
<TeamNext id={props.teamID} />
</div>
)}
{previousGamesStatus === "error" ? (
console.log("unable to load previous games")
) : (
<div className="previousGames">
<TeamPrevious />
</div>
)}
</div>
</div>
);
}
export default TeamContent;
infoActions.js
import { API_URL } from "../Api";
import { infoActions } from "./infoSlice";
export function fetchTeams() {
return (dispatch) => {
dispatch(infoActions.loadStatusHandler({ status: "loading" }));
async function getTeams() {
try {
const rq = await fetch(`${API_URL}Lookup_all_teams.php?id=4387`);
const res = await rq.json();
const data = res.teams;
dispatch(infoActions.loadTeamsHandler({ teams: data }));
dispatch(infoActions.loadStatusHandler({ status: "done" }));
} catch (error) {
dispatch(infoActions.loadStatusHandler({ status: "error" }));
}
}
getTeams();
};
}
export function fetchTeamPlayers(id) {
return (dispatch) => {
async function getPlayers() {
dispatch(infoActions.statusPlayersHandler({ status: "loading" }));
try {
const rq = await fetch(`${API_URL}lookup_all_players.php?id=${id}`);
const res = await rq.json();
const data = res.player;
dispatch(infoActions.loadPlayersHandler({ players: data }));
dispatch(infoActions.statusPlayersHandler({ status: "done" }));
} catch (error) {
dispatch(infoActions.statusPlayersHandler({ status: "error" }));
}
}
getPlayers();
};
}
export function fetchUpcomingGames(id) {
return (dispatch) => {
dispatch(infoActions.statusUGHandler({ status: "loading" }));
async function getGames() {
try {
const rq = await fetch(`${API_URL}eventsnext.php?id=${id}`);
const res = await rq.json();
const data = res.events;
dispatch(infoActions.upcomingGamesHandler({ games: data }));
dispatch(infoActions.statusUGHandler({ status: "done" }));
} catch (error) {
dispatch(infoActions.statusUGHandler({ status: "error" }));
}
}
getGames();
};
}
export function fetchPreviousGames(id) {
return (dispatch) => {
dispatch(infoActions.statusPGHandler({ status: "loading" }));
async function getGames() {
try {
const rq = await fetch(`${API_URL}eventslast.php?id=${id}`);
const res = await rq.json();
const data = res.results;
dispatch(infoActions.previousGamesHandler({ games: data }));
dispatch(infoActions.statusPGHandler({ status: "done" }));
} catch (error) {
dispatch(infoActions.statusPGHandler({ status: "error" }));
}
}
getGames();
};
}
export function fetchLiveGames() {
return (dispatch) => {
dispatch(infoActions.statusLiveGames({ status: "loading" }));
async function getGames() {
try {
const rq = await fetch(
`https://www.thesportsdb.com/api/v2/json/40130162/livescore.php?l=4387`
);
const res = await rq.json();
const data = res.events;
dispatch(infoActions.statusLiveGames({ status: "done" }));
dispatch(infoActions.loadLiveGames({ liveGames: data }));
} catch (error) {
dispatch(infoActions.statusLiveGames({ status: "error" }));
}
}
getGames();
};
}
Try remove dispatch from the array you passed to
useEffect(() => {
...
}, [dispatch, props.teamID])
and
useEffect(() => {
...
}, [dispatch])
dispatch is a function, and if you include it into the useEffect listener, the useEffect will trigger on every render

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.

empty cart, firebase-firestore react-hooks

I'm developing an ecommerce app with Firestore.
When a user deletes an item or adds an item - it's rendering properly.
But when I'm trying to empty the whole cart (delete doc), it's just not rendering without refresh.
Maybe it's about the deleted doc, so the the function cannot find the doc because there is no doc?
If so, what would be best practice solution here?
Here is the code:
import React, { useState, useEffect } from 'react'
import firebase from 'firebase';
import { useAuth, useStoreUpdate } from '../contexts/FirebaseContext';
import { Link, useHistory } from 'react-router-dom';
import 'react-responsive-modal/styles.css';
import { Modal } from 'react-responsive-modal';
export default function Cart() {
const [userMail, setUserMail] = useState(undefined)
const [userCart, setUserCart] = useState(undefined)
const [totalAmmout, setTotalAmmout] = useState(0)
const user = useAuth()
const userDoc = firebase.firestore().collection("cart").doc(userMail)
const updateStore = useStoreUpdate()
const [open, setOpen] = useState(false);
const onOpenModal = () => setOpen(true);
const onCloseModal = () => setOpen(false);
const history = useHistory()
const emptyCart = async () => {
await userDoc.delete()
await updateCart()
await console.log('ksaljdklasd');
await updateStore()
await console.log('dasdsad');
}
const updateCart = () => {
userDoc.get().then((doc) => {
if (doc.exists) {
let cart = doc.data()
setUserCart(cart)
}
})
}
const updateData = async () => {
if (user.currentUser) {
await updateCart()
if (userCart) {
let totalPrice = 0;
await userCart.item.forEach(item => {
totalPrice += item.price
})
await setTotalAmmout(totalPrice)
}
}
}
async function removeFromCart(itemId, name, url, price, category, type, description) {
const cartItem = { itemId, name, url, price, category, type, description }
await userDoc.update({
item: firebase.firestore.FieldValue.arrayRemove(cartItem)
})
await updateCart()
await updateStore()
}
useEffect(() => {
if (user.currentUser) {
setUserMail(user.currentUser.email);
updateStore();
}
}, []);
useEffect(() => {
updateData().then(
console.log(totalAmmout)
)
}, userCart);
if (!userCart) return <h1>hold</h1>
return (
<main className="main-cart">
<div className="container">
{userCart.item && userCart.item.length >= 1 && userCart.item.map((item) => {
return (
< div className="item-container" key={item.itemId} >
<h3>{item.name}</h3>
<p>${item.price}</p>
<img height="150px" width="150px" src={item.url} alt="" />
<button onClick={async () => {
await removeFromCart(item.itemId, item.name, item.url, item.price, item.category, item.type, item.description)
}}>X</button>
</div>
)
})}
</div>
<button className="fixed-bottom-link" onClick={onOpenModal}>finish</button>
<Modal showCloseIcon={true} open={open} onClose={onCloseModal} center>
<div className="modal-container">
<div>
{userCart &&
userCart.item.map(item => {
return (
<li>{item.name} <span className="strong">{'|$' + item.price}</span></li>
)
})
}
{totalAmmout &&
<h3>total price: ${totalAmmout}</h3>
}
</div>
<button onClick={emptyCart}>Click to Pay</button>
</div>
</Modal>
</main >
)
}
just had to reset the doc :
befor :
const emptyCart = async () => {
await userDoc.delete()
await updateCart()
await updateStore()
}
after :
const emptyCart = async () => {
const userDoc = await firebase.firestore().collection("cart").doc(userMail)
await userDoc.delete()
await userDoc.set({
item: firebase.firestore.FieldValue.arrayUnion()
})
await updateCart()
await updateStore()
}

Trying to setState with React Hooks, using axios.. Not getting data

I'm using an axios call to a database to get "about me" data, for client to update. DB is connected properly, as I am able to log in just fine, I've isolated this issue pretty well to my GET request.
My context provider file:
import React, { useState } from 'react'
import axios from 'axios'
export const UserContext = React.createContext()
const userAxios = axios.create()
userAxios.interceptors.request.use((config) => {
const token = localStorage.getItem("token")
config.headers.Authorization = `Bearer ${token}`
return config
})
const UserProvider = (props) => {
const initState = {
user: JSON.parse(localStorage.getItem("user")) || {},
token: localStorage.getItem("token") || "",
authErrMsg: ""
}
const [userState, setUserState] = useState(initState)
const [dataState, setDataState] = useState({
bioData: []
})
const login = credentials => {
axios.post("/auth/login", credentials)
.then(res => {
const { user, token } = res.data
localStorage.setItem("user", JSON.stringify(user))
localStorage.setItem("token", token)
setUserState(res.data)
})
.catch(err => handleAuthErr(err.response.data.errMsg))
}
const handleAuthErr = errMsg => {
setUserState(prevUserState => ({
...prevUserState,
authErrMsg: errMsg
}))
}
const logout = () => {
localStorage.removeItem("token")
localStorage.removeItem("user")
setUserState({
user: {},
token: "",
authErrMsg: ""
})
}
const getData = () => {
axios.get('/info/bio')
.then(res => {
setDataState(prevData => ({
...prevData,
bioData: res.data
}))
})
.catch(err => {
console.log(err)
})
}
const deleteBio = (id) => {
userAxios.delete(`/api/bio/${id}`)
.then(res => {
setDataState(prevData => ({
...prevData,
bioData: dataState.bioData.filter(bio => bio._id !== id)
}))
})
.catch(err => console.log(err.response.data.errMsg))
}
const addBio = (newText) => {
const newBio = {
bioText: newText
}
userAxios.post('/api/bio', newBio)
.then(res => {
getData()
})
.catch(err => console.log(err))
}
const editBio = (update, id) => {
const updatedBio = {
bioText: update
}
userAxios.put(`/api/bio/${id}`, updatedBio)
.then(res => {
console.log(res.data, 'edited')
getData()
})
.catch(err => console.log(err))
}
return (
<UserContext.Provider
value={{
user: userState.user,
token: userState.token,
authErrMsg: userState.authErrMsg,
login: login,
logout: logout,
getData: getData,
dataState: dataState,
editBio: editBio,
deleteBio: deleteBio,
addBio: addBio
}}>
{props.children}
</UserContext.Provider>
)
}
export default UserProvider
Here's my Bio component. The loading effect never changes because for some reason, no "bioData" is saving, in the provider. Tested it with that little button/handleClick and coming up an empty array.
import React, {useContext, useState, useEffect} from 'react'
import { UserContext } from './context/userProvider'
const Bio = () => {
const { token, editBio, dataState: {bioData} } = useContext(UserContext)
const [loader, setLoader] = useState('Loading')
useEffect(() => {
if(bioData[0]?._id === undefined){
setLoader('Loading')
}else {
setLoader(bioData[0]?._id)
}
})
// let initText = bioData[0].bioText
const [bioText, setBioText] = useState("initText")
const handleChange = (e) => {
setBioText(e.target.value)
}
const handleUpdate = () => {
editBio(bioText, bioData[0]._id)
alert`Bio successfully updated. :)`
}
const handleClick = () => {
console.log(bioData)
}
return (
<div className='bio'>
<h1>About Me</h1>
<div className='bio-content'>
{loader === 'Loading' ?
<div>
<p>Loading...</p>
<button onClick={handleClick}>thing</button>
</div>
:
<>
{token ?
<div className="editBio">
<p>edit mee</p>
</div>
:
<h4>{bioData[0].bioText}</h4> }
</>
}
</div>
</div>
)
}
export default Bio
Thanks in advance guys! Let me know if I can post routes or anything that might be helpful.

View is not updating after adding element to MongoDB

[SOLVED]
I'm trying to make a small application with Redux/React/Mongo/Typescript and faced a problem, that when I'm adding element to database, I can see new added row in a table but without values in it. But after refreshing the page, values are there.
I think that this is because the array with records immediately refreshes, while new element is not in database yet. I used await/async but it didn't solve this problem. Can anyone help me with this?
Action.ts
export const getAllTeams: ActionCreator<ThunkAction<Promise<any>,
ITeam[],
null,
ITeamGetAllAction
>> = () => {
return async (dispatch: Dispatch) => {
await axios.get('http://localhost:5000/teams/')
.then(res => {
dispatch({
teams: res.data,
type: TeamActionsTypes.GET_ALL,
})
}
);
};
};
export const addTeam: ActionCreator<ThunkAction<Promise<any>,
ITeamAddTeamAction,
ITeam,
ITeamAddTeamAction
>> = (team: ITeam) => {
return async (dispatch: Dispatch) => {
await axios.post('http://localhost:5000/teams/add', team)
.then(res => {
dispatch({
type: TeamActionsTypes.ADD_TEAM,
result: res,
});
})
};
};
Reducer.ts:
export const teamReducer: Reducer<ITeamState, TeamActions> = (
state = initialTeamState,
action,
) => {
switch (action.type) {
case TeamActionsTypes.GET_ALL: {
return {
...state,
teams: action.teams,
};
}
case TeamActionsTypes.ADD_TEAM: {
return{
...state,
teams: [action.result,...state.teams]
}
}
case TeamActionsTypes.GET_ONE: {
return{
...state,
}
}
default:
return state;
}
};
Component.tsx
interface RatingTableProps {
getTeams: () => Promise<TeamActionsTypes.GET_ALL>;
teams: ITeam[];
}
const RatingTable: React.FC<RatingTableProps> = ({
getTeams,
teams
}) => {
useEffect(()=>{
getTeams();
},[]);
return (
<table className="striped">
<thead>
<tr>
<th>Team</th>
<th className="right-align">Clicks</th>
</tr>
</thead>
<tbody>
{teams && teams.map(team => {
return <>
<tr key={team.name}>
<td>{team.name}</td>
<td className="right-align">{team.count}</td>
</tr>
</>
})}
</tbody>
</table>
)
};
const mapStateToProps = (store: IAppState) => {
return {
teams: store.teamState.teams,
};
};
const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => {
return {
getTeams: () => dispatch(getAllTeams()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(RatingTable);
ComponentAdd.tsx
interface RatingFormProps{
addTeam: (team: ITeam) => Promise<TeamActionsTypes.ADD_TEAM>;
}
const RatingForm: React.FC<RatingFormProps> = ({
addTeam
})=> {
const [teamName, setTeamName] = useState<string>('');
const changeHandle = (event: React.ChangeEvent<HTMLInputElement>) => {
setTeamName(event.target.value);
};
const handleSubmit = (event: React.FormEvent) =>{
event.preventDefault();
addTeam({
name: teamName,
count: 0,
});
setTeamName('')
};
return (
<div className="row">
<form onSubmit={handleSubmit}>
<div className="inputField col s6">
<label htmlFor="teamName" className="active">
Name your team:
</label>
<input
onChange={changeHandle}
value={teamName}
type="text"
id="teamName"
placeholder="Best team name ever"
/>
</div>
<div className="col s6">
<button className="btn-large waves-effect waves-light" type="submit" name="action">Submit
</button>
</div>
</form>
</div>
)
};
const mapDispatchToProps = (dispatch: ThunkDispatch<any, any, AnyAction>) => {
return {
addTeam: (team: ITeam) => dispatch(addTeam(team)),
};
};
export default connect(
null,
mapDispatchToProps,
)(RatingForm);
teams.js(Routes)
router.route('/').get(async (req, res) => {
await Team.find()
.then(teams => res.json(teams))
.catch(err => res.status(400).json('Error: ' + err));
});
router.route('/add').post(async (req, res) => {
const name = req.body.name;
const count = 0;
const newTeam = new Team({name, count,});
await newTeam.save()
.then(() => res.json('Team added to database!'))
.catch(err => res.status(400).json('Error: ' + err));
});
How it looks in UI:
before adding new element
after adding new element
If you need any additional information or code, please let me know. Thank you!
UPDATE 1:
Thank to the comment, of course one big error is to return actual object after posting, not a string(Shame on me)
router.route('/add').post(async (req, res) => {
const name = req.body.name;
const count = 0;
const newTeam = new Team({name, count,});
await newTeam.save()
.then((team) => res.json(team))
.catch(err => res.status(400).json('Error: ' + err));
});
UPDATE 2:
OMG this was such a stupid error
The previous update solved an issue, but after I've recieved undefiened value from dipatch and this was because I was returning Promis, but not value.
export const addTeam: ActionCreator<ThunkAction<Promise<any>,
ITeamAddTeamAction,
ITeam,
ITeamAddTeamAction
>> = (team: ITeam) => {
return async (dispatch: Dispatch) => {
await axios.post('http://localhost:5000/teams/add', team)
.then(res => {
dispatch({
type: TeamActionsTypes.ADD_TEAM,
result: res.data, //<- Instead of just res
});
})
};
};
Thanks to everyone, who spent some time on it, stupid errors as always.
You should return the updated team from your back-end API. You can modify your API like this.
router.route('/add').post(async (req, res) => {
const name = req.body.name;
const count = 0;
const newTeam = new Team({name, count,});
await newTeam.save()
.then(team => res.json(team))
.catch(err => res.status(400).json('Error: ' + err));
});
Drop a comment if the problem still persists.

Resources