How to avoid this message warning "Maximum update depth exceeded..." on NextJs - reactjs

on NextJs i not understand, how useEffect work. What i need to do, to stop of receiving this warning message
"Maximum update depth exceeded":
The Code bellow is the page, that call a component ListContainer, this page add a item to container.
The page JSX:
import { useState } from "react";
import AppLayout from "../components/AppLayout";
import ListContainer from "../components/ListContainer";
export default function componentCreator(){
const [item,setItem] = useState([])
/* add item to container */
function addItem(){
let newItem = item
newItem.push({
produto: 'Skol 350ml',
preco: '1200,00',
quantidade: 'cx c/ 15 unidades'
})
setItem(newItem)
}
return (
<AppLayout>
<ListContainer items={item} setItems={setItem}/>
<div className="productcardbuttonshow" onClick={() => addItem()}>ADICIONAR</div>
</AppLayout>
)
}
Bellow the component that handle the items, remove or add. But it works, but on console trigger warning messages about update.
Component ListContainer.jsx:
import { useState,useEffect } from "react";
export default function ListContainer(props){
const [html,setHTML] = useState(null)
const [item,setItem] = useState(props.items)
/* refresh html container */
useEffect(() => {
const itemHTML = item.map((itemmap,id) => {
return (
<div id={id} onClick={() => delItem(id)} className="itemProposta">
{itemmap.produto} - {itemmap.quantidade} - R$ {itemmap.preco}
</div>
)
})
setHTML(itemHTML)
})
/* remove item from container */
function delItem(id){
let itemlist = props.items
itemlist.splice(id,1)
props.setItems(itemlist)
}
return (
<>
{html}
</>
)
}

You are getting into an infinite loops of renders. This code is responsible:
useEffect(() => {
const itemHTML = item.map((itemmap,id) => {
return (
<div id={id} onClick={() => delItem(id)} className="itemProposta">
{itemmap.produto} - {itemmap.quantidade} - R$ {itemmap.preco}
</div>
)
})
setHTML(itemHTML)
})
This callback inside useEffect will run after every render, because there is no dependency array. That means after every render, setHTML(itemHTML) is called. And even if the constituent objects of the array itemHTML are same, a new reference of the array is created. A new reference is created because .map() returns a new reference of the array. And although render and update works correctly, infinite rendering is happening.
Consider adding a dependency array to useEffect. For example:
useEffect(() => {
/* function body */
},[props.items]);
Now useEffect callback only runs if props.items reference changes.
Side note (unrelated to your question):
In the below code,
function addItem(){
let newItem = item
newItem.push({
produto: 'Skol 350ml',
preco: '1200,00',
quantidade: 'cx c/ 15 unidades'
})
setItem(newItem)
}
You should do let newItem = [...item], otherwise you are not creating a new reference of item array and setItem(newItem) is basically useless in that case.

Related

Why is my component failing to run when I call it?

I am struggling to find why my component is not responding to being called by its parent. I am trying to integrate Cloud Firestore with code that previously ran using Redux. My first goal is to populate my List with data from Firestore.
Here are my (simplified) components in question:
// List.js
import React, { useEffect, useState } from "react";
import db from "../../db";
import { onSnapshot, query, collection, orderBy } from "firebase/firestore";
import TaskItem from "./TaskItem";
const List = () => {
const [taskList, setTaskList] = useState([]); // Currently assumes DB never empty, populates on initial render
const [isInitialRender, setIsInitialRender] = useState(true);
// Firestore
const ref = collection(db, "Tasks");
const q = query(ref, orderBy("listIndex"));
useEffect(() => {
// Execute only on initial render
if (isInitialRender) {
// Populate task list
onSnapshot(q, (querySnapshot) => {
setTaskList(() => querySnapshot.docs)
}, (error) => {
console.log(error)
})
};
setIsInitialRender(() => false);
}, []);
return (
<>
<h2>List</h2>
{taskList.forEach((task) => ( // console-logging `task` here will output correct data
<ul key={task.data().key}>
<TaskItem
id={task.data().key}
// docRef={taskDoc}
/>
</ul>
))
}
</>
);
};
export default List;
// TaskItem.js
import React from "react";
const TaskItem = (props) => {
console.log('This will not print')
const submitHandler = () => console.log('Submitted');
return (
<form onSubmit={submitHandler}>
<input
autoFocus
type="text"
/>
</form>
);
};
export default TaskItem;
I have tried:
Populating the state with the data from each document (rather than assigning it directly), then passing the contents as props. This led to (I believe) an infinite loop, and ideally I would like to pass the actual DocumentReference to the TaskItem anyways. So this was a bust for me.
Returning [...querySnapshot.docs], or even (prev) => prev = [...querySnapshot.docs] in the state setter. No response from TaskItem().
Decomposing the taskList state into a new dummy array, and using that array to populate the props for TaskItem.
I know that the task data is being fetched successfully because I can satisfactorily log taskList's contents from the map function in List's return statement. But it seems like TaskItem() never runs.
Does anyone see my error here?
edit: sorry I assumed you were using map. I'm not sure why your forEach isn't working but map would work, from my example
edit 2: you probably are looking to use map because you want to transform every element in the array: JavaScript: Difference between .forEach() and .map()
you forgot to return something from the map, and maybe need {} instead.
try
{taskList.forEach((task) => {
return (
<ul key={task.data().key}>
<TaskItem
id={task.data().key}
// docRef={taskDoc}
/>
</ul>
)
})

React Slider images changing on Click not working (uncaught error too many re-renders)

I'm trying to create a React slider for images. But I'm getting an error that says Uncaught Error: Too many re-renders. If anyone can just point me in the right direction I would really appreciate it. I'm certain that the issue lays within the onClick aspect of the sliderDots mapping.
import React, { useEffect, useState } from 'react';
import Sliderdots from '../CarasouelDots/Sliderdots.component';
import './Slider.styles.scss'
import sliderImages from '../../MockImages/mockimages';
const Slider = () => {
const images = sliderImages;
//Iterator
const [img, setImg] = useState(0);
//Getting all shoe images from an object array
const shoes = images.map(i => (i.shoe));
const heading = images.map(i => (i.title));
const content = images.map(i => (i.content))
const numbers = shoes.map((i, index) => (index))
const indexSet = (number) =>{
setImg(number);
}
//problem with onClick here??
const sliderD = images.map((dot, index) => <Sliderdots key={index} onClick={indexSet(index)}/>);
useEffect(() => {
const timer = setTimeout(() => {
img == shoes.length - 1 ? setImg(0) : setImg(img + 1)
}, 4500)
}, [img]);
return (
<div className='slider-container' style={{ backgroundImage: `url(${shoes[img]})` }}>
<div className='overlay'>
<h1 className='introduction'>{heading[img]}</h1>
<p className='content'>{content[img]}</p>
<div className='dot-container'>
{sliderD}
</div>
</div>
</div>
);
};
export default Slider;
The reason why your component is constantly rerendering is because your onClick property is actually a function call in disguise that gets executed every render:
// This line actually calls the `indexSet` function each time!
const sliderD = images.map((dot, index) => <Sliderdots key={index} onClick={indexSet(index)}/>);
And since indexSet updates the state of the React component by calling setImg, the React component will always end up re-rendering when it reaches that line of code, and since that line of code always re-calls the indexSet function, your component will infinitely re-render.
To fix your code, you just need to replace that onClick property with an anonymous function:
const sliderD = images.map((dot, index) => <Sliderdots key={index} onClick={() => indexSet(index)}/>);

React updating display of a page's H1 state with useState and useEffect hooks

After reading the docs for useState and useEffect I cant figure out what i'm doing wrong here... Im trying to dynamically update my h1 title with an updated title when a tab is clicked, however the state will not update so my title wont rerender.
This is my subheader component which takes in an array of objects as props. These objects are iterated over and used to populate the subnav bar. (which works as intended).
const Subheader = (props) => {
const {
submenuItems = []
} = props;
// State
const [pageTitle, setPageTitle] = useState(submenuItems[0].name); //Sets starting value as the first item of my submenu which is also the default route so works as intended.
const handleMenuItemClick = (name) => {
setPageTitle(name)
console.log(name) //This prints out the updated expected value
console.log(pageTitle) //This prints out the original not updated value
}
const submenuItemsJSX = submenuItems.map((item, index) => {
return (
<li
key={index}
to={item.to}
onClick={() => handleMenuItemClick(item.name)}
>
<a>
{item.name}
</a>
</li>
)
});
useEffect(() => {
console.log(pageTitle) //This prints out the original not updated value
}, [pageTitle])
return (
<div>
<div>
<h1>
{pageTitle} //This is what i want to update
</h1>
</div>
<div>
<ul>
{submenuItemsJSX}
</ul>
</div>
</div>
)
}
export default Subheader
a sample of whats coming in through the subMenuItems:
{name: 'Categories', to: '/settings/categories', buttons: [] }
setSelectedMenuItem and setPageTitle are the asynchronous method, and you can't get the updated value of selected and pageTitle immediately after setSelectedMenuItem() and setPageTitle().
You should use useEffect to check the updated value with adding dependency.
useEffect(() => {
console.log(selected)
}, [selected])
useEffect(() => {
console.log(pageTitle)
}, [pageTitle])
Your code appears to be correct. The issue must be somewhere else.
I've made a codesandbox demo and everything works.
Have you tried to pass empty string '' to useState, when you declare the pageTitle? And for initialisation of the value you can use useEffect hook.
const [pageTitle, setPageTitle] = useState('');
useEffect(() => {
setPageTitle(submenuItems[0].name)
})

React Hook useEffect() run continuously although I pass the second params

I have problem with this code
If I pass the whole pagination object to the second parameters of useEffect() function, then fetchData() will call continuously. If I only pass pagination.current_page so It will call only one time, but when I set new pagination as you see in navigatePage() function, the useEffect() does not call to fetchData() although pagination has changed.
How to solve this. Thank you very much!
Besides I do not want the use useEffect() call when first time component mounted because the items is received from props (It is fetch by server, this is nextjs project).
import React, {useEffect, useState} from 'react';
import Filter from "../Filter/Filter";
import AdsListingItem from "../AdsListingItem/AdsListingItem";
import {Pagination} from "antd-mobile";
import styles from './AdsListing.module.css';
import axios from 'axios';
const locale = {
prevText: 'Trang trước',
nextText: 'Trang sau'
};
const AdsListing = ({items, meta}) => {
const [data, setData] = useState(items);
const [pagination, setPagination] = useState(meta);
const {last_page, current_page} = pagination;
const fetchData = async (params = {}) => {
axios.get('/ads', {...params})
.then(({data}) => {
setData(data.data);
setPagination(data.meta);
})
.catch(error => console.log(error))
};
useEffect( () => {
fetchData({page: pagination.current_page});
}, [pagination.current_page]);
const navigatePage = (pager) => {
const newPagination = pagination;
newPagination.current_page = pager;
setPagination(newPagination);
};
return (
<>
<Filter/>
<div className="row no-gutters">
<div className="col-md-8">
<div>
{data.map(item => (
<AdsListingItem key={item.id} item={item}/>
))}
</div>
<div className={styles.pagination__container}>
<Pagination onChange={navigatePage} total={last_page} current={current_page} locale={locale}/>
</div>
</div>
<div className="col-md-4" style={{padding: '15px'}}>
<img style={{width: '100%'}} src="https://tpc.googlesyndication.com/simgad/10559698493288182074"
alt="ads"/>
</div>
</div>
</>
)
};
export default AdsListing;
The issue is you aren't returning a new object reference. You save a reference to the last state object, mutate a property on it, and save it again.
const navigatePage = (pager) => {
const newPagination = pagination; // copy ref pointing to pagination
newPagination.current_page = pager; // mutate property on ref
setPagination(newPagination); // save ref still pointing to pagination
};
In this case the location in memory that is pagination remains static. You should instead copy all the pagination properties into a new object.
const navigatePage = (pager) => {
const newPagination = {...pagination}; // shallow copy into new object
newPagination.current_page = pager;
setPagination(newPagination); // save new object
};
To take it a step further you really should be doing functional updates in order to correctly queue up updates. This is in the case that setPagination is called multiple times during a single render cycle.
const navigatePage = (pager) => {
setPagination(prevPagination => {
const newPagination = {...prevPagination};
newPagination.current_page = pager;
});
};
In the case of pagination queueing updates may not be an issue (last current page set wins the next render battle), but if any state updates actually depend on a previous value then definitely use the functional update pattern,

Why is my state variables being rendered twice in reactjs

Every time I use react and the useEffect method my state variable renders twice. Once an empty variable and the next the desired variable. What can I try to help avoid this problem for now and in the future?
import React, { useState,useEffect } from "react";
export default function Member (props) {
const [team,setTeam] = useState([]);
useEffect(() => {
let array = ["hello","hi"];
setTeam(array);
}, [])
console.log(team);
return (
<>
{team.forEach(i => <p>{i}</p>)}
</>
)
}
You need to use map to render an array in JSX:
export default function Member(props) {
const [team, setTeam] = useState([]);
useEffect(() => {
let array = ["hello", "hi"];
setTeam(array);
}, []);
console.log(team);
return (
<>
{team.map(i => ( // use map here
<p>{i}</p>
))}
</>
);
}
forEach doesn't return anything, so you can't use it to render components like that.
Also in your code instead of using useEffect to setup initial state, you can just set it straight in useState:
export default function Member(props) {
const [team, setTeam] = useState(["hello", "hi"]);
console.log(team);
return (
<>
{team.map(i => ( // use map here
<p>{i}</p>
))}
</>
);
}
It is an abvious behavior.
Component render's first time with initial state. After useEffect (componentDidMount) again re-renders.
So you are getting two console.log.
To avoid this, you need to set the state initally,
const [team,setTeam] = useState(["hello","hi"]);
and remove useEffect.
Note: forEach won't print data, you need map here,
{team.map(i => <p key={i}>{i}</p>)} //provide a key here

Resources