React - Maximum update depth exceeded when setting state in use effect - javascript

So in my database there are incoming calls, outgoing calls, meetings and emails. In this component, I want to get all of them and combine them into one array to fill my datatable and create a "latest activity" table. I have fetched each type from my redux store and have a useEffect for each to append it to the data that fills the datatable. It all works fine and I get the result I want however the error in the title appears and turns my page into a vegetable afterwards.
const [incomingCalls, setIncomingCalls] = useState([])
const [outgoingCalls, setOutgoingCalls] = useState([])
const [meetings, setMeetings] = useState([])
const [emails, setEmails] = useState([])
const [showIncoming, setShowIncoming] = useState(false);
const [showId, setShowId] = useState('')
const [data, setData] = useState([...incomingCalls])
const incomingCallList = useSelector((state) => state.incomingCallList);
const {
loading: loadingCalls,
error: errorIncCalls,
incomingCalls: incCalls,
} = incomingCallList;
const outgoingCallList = useSelector((state) => state.outgoingCallList);
const {
loading: loadingOutCalls,
error: errorOutCalls,
outgoingCalls: outCalls,
} = outgoingCallList;
const meetingList = useSelector((state) => state.meetingList);
const {
loading: loadingMeetings,
error: errorMeetings,
meetings: meetings_list,
} = meetingList;
const emailList = useSelector((state) => state.emailList);
const {
loading: loadingEmails,
error: errorEmails,
emails: email_list,
} = emailList;
useEffect(() => {
if(props.contractor._id) {
dispatch(listIncomingCalls())
dispatch(listOutgoingCalls())
dispatch(listMeetings())
dispatch(listEmails())
}
}, [props.contractor._id, dispatch])
const showIncomingModalHandler = id => {
console.log(id)
}
useEffect(() => {
if(incCalls) {
const transformedIncomingCalls = incCalls.filter(call => call.contractor._id === props.contractor._id).map(c => ({
type: 'Incoming Call',
contact_person: c.contact_person,
logged_by: c.created_by.name,
date: c.time_created,
link: <Button onClick={() => showIncomingModalHandler(c._id)}><Eye /></Button>
}))
setIncomingCalls(transformedIncomingCalls)
setData([...incomingCalls])
}
}, [incCalls, incomingCalls, props.contractor._id])
useEffect(() => {
if(outCalls) {
const transformedOutgoingCalls = outCalls.filter(call => call.contractor._id === props.contractor._id).map(c => ({
type: 'Outgoing Call',
contact_person: c.contact_person,
logged_by: c.created_by.name,
date: c.time_created,
link: <> <LinkContainer to={`/outgoing-calls/${c._id}`} className='text-success' style={{ cursor: 'pointer'}}><Eye /></LinkContainer> </>
}))
setOutgoingCalls(transformedOutgoingCalls)
setData([...incomingCalls, ...outgoingCalls])
}
}, [outCalls, incomingCalls, outgoingCalls, props.contractor._id])
useEffect(() => {
if(meetings_list) {
const transformedMeetings = meetings_list.filter(meeting => meeting.contractor._id === props.contractor._id).map(m => ({
type: 'Meeting',
contact_person: m.held_with,
logged_by: m.logged_by.name,
date: m.meeting_time,
link: <> <LinkContainer to={`/meeting/${m._id}`} className='text-success' style={{ cursor: 'pointer'}}><Eye /></LinkContainer> </>
}))
setMeetings(transformedMeetings)
setData([...incomingCalls, ...outgoingCalls, ...meetings])
}
}, [meetings_list, incomingCalls, outgoingCalls, meetings, props.contractor._id])
useEffect(() => {
if(email_list) {
const transformedEmails = email_list.filter(email => email.contractor._id === props.contractor._id).map(e => ({
type: 'Email',
contact_person: e.contact_person,
logged_by: e.logged_by.name,
date: e.date,
link: <> <LinkContainer to={`/emails/${e.contractor._id}/`} className='text-success' style={{ cursor: 'pointer'}}><Eye /></LinkContainer> </>
}))
setEmails(transformedEmails)
setData([...incomingCalls, ...outgoingCalls, ...meetings, ...emails])
}
}, [email_list, incomingCalls, outgoingCalls, meetings, emails, props.contractor._id])
const columns = [
{ name: 'Action', selector: 'link', sortable: true },
{ name: 'Activity Type', selector: 'type', sortable: true },
{ name: 'Contact Person', selector: 'contact_person', sortable: true },
{ name: 'Logged By', selector: 'logged_by', sortable: true },
{ name: 'Date', selector: 'date', sortable: true }
]

The values which you try to pass in the dependency array of react has to be primitive . It should not be array or object.
For example, consider this useEffect
useEffect(() => {
if(incCalls) {
const transformedIncomingCalls = incCalls.filter(call => call.contractor._id === props.contractor._id).map(c => ({
type: 'Incoming Call',
contact_person: c.contact_person,
logged_by: c.created_by.name,
date: c.time_created,
link: <Button onClick={() => showIncomingModalHandler(c._id)}><Eye /></Button>
}))
setIncomingCalls(transformedIncomingCalls)
setData([...incomingCalls])
}
}, [incCalls, incomingCalls, props.contractor._id])
when this useEffect runs you are setting the state with
setIncomingCalls(transformedIncomingCalls)
setData([...incomingCalls])
Once there is state update your entire function will re-run. Now in your code you have this
const {
loading: loadingCalls,
error: errorIncCalls,
incomingCalls: incCalls,
} = incomingCallList;
Now for every re-render you are creating a new incCalls which is array. So when you are passing a dependency as an array react does shallow comparison. It checks whether the previous reference of this array is same as the new one ? . But here it is not because the reference changes each time for every re-render . So your useEffect will run again and this leads to infinite re-render and the maximum stack exceeded error.
Avoid passing arrays or objects as a dependency in the array. You can modify your dependency array as below . This will trigger the useEffect only when the length of inCalls or incoming is changed
[incCalls.length, incomingCalls.length, props.contractor._id]

Related

gain access of the variable that stores the useSelector in my handler function

I am trying to access the students variable by putting its value in a state then accessing that state in my handler function but it always returns an empty array.
const students = useSelector((state) => state.students);
useEffect(() => {
setdata(students);
console.log(data);
}, [])
const handleScanWebCam = (result, error) => {
if (result) {
console.log(data)
const list = data.filter(function (student) {
return student.id === result.text;
})
setScanResultWebCam({
id: list.id,
grade: list.grade,
name: list.name,
section: list.section
});
}
}
this is my full code
function QrScanner() {
const dispatch = useDispatch(getStudents())
useEffect(() => {
dispatch(getStudents());
}, [dispatch])
const students = useSelector((state) => state.students);
const [scanResultWebCam, setScanResultWebCam ] = useState({
id: '',
grade: '',
name: '',
section:''
});
const [openVerifier, setOpenVerifier] = useState(false);
const [data, setdata] = useState([]);
useEffect(() => {
setdata(students);
console.log(data);
}, [])
const handleScanWebCam = (result, error) => {
if (result) {
const list = data.filter(function (student) {
return student.id === result.text;
})
setScanResultWebCam({
id: list.id,
grade: list.grade,
name: list.name,
section: list.section
});
setOpenVerifier(true);
}
}
return (
<>
<NavBar />
<Container
sx={{
display: 'flex',
marginTop: '4rem',
flexWrap: 'wrap',
gap: '12px',
width: '90%'
}}
>
<Box
sx={{
width: '50%',
border: 'solid',
display: 'flex',
flex: '1'
}}
>
<QrReader
scanDelay={500}
containerStyle={{ width: '25rem', margin: 'auto'}}
onResult={handleScanWebCam}
// onError={handleErrorWebcam}
/>
</Box>
<PopupVerifier
details={scanResultWebCam}
verifier={openVerifier}
handleClose={() => handleClose()}
/>
</Container>
</>
)
}
If you need to cache a "duplicate" of the selected students state then I'd recommend caching students in a React ref that can have its current value read at any time during the component lifecycle, especially in stale closures.
Example:
function QrScanner() {
const dispatch = useDispatch(getStudents());
useEffect(() => {
dispatch(getStudents());
}, [dispatch]);
const students = useSelector((state) => state.students);
const studentsRef = React.useRef(students);
useEffect(() => {
studentsRef.current = students;
}, [students]);
...
const handleScanWebCam = (result, error) => {
if (result) {
const list = studentsRef.current.find(
(student) => student.id === result.text
);
if (list) {
setScanResultWebCam(list);
}
setOpenVerifier(true);
}
};
return (
...
);
}
You have using from react-redux. so you dont need to set student state.
and dispatch is used for change in store not getting data from it.
if you want to get an item from array use find instead of filter :
const list = students.find(student=>student.id===result.text);
i edit your code to :
function QrScanner() {
const students = useSelector((state) => state.students);
const [scanResultWebCam, setScanResultWebCam ] = useState({
id: '',
grade: '',
name: '',
section:''
});
const [openVerifier, setOpenVerifier] = useState(false);
const handleScanWebCam = (result, error) => {
if(result) {
const list = students.find(student=> student.id === result.text);
setScanResultWebCam({id: list.id, grade: list.grade, name: list.name, section: list.section});
setOpenVerifier(true);
}
}
return(
...
)
}

I need to set default value of array of object reactjs

I am getting an array or an object from the backend what my task is to set the first index mean [0] to set in default value but when I set I am getting undefined you can see in code userL taking my array of an object but when I print userle it's showing label: undefined value: undefined or when I print userL I'm getting my array of object list
const [userL, setUserL] = useState([]);
useEffect(() => {
axios
.get(`${DJANGO_SERVER_ADDRESS}/auth/analyst/`)
.then((res) => {
setUserL(res.data);
})
.then(
(result) => {
},
(error) => {
}
);
}, []);
console.log("ena1", userL);
const [userle, setUserle] = useState(
{ value: userL[0].username,
label: userL[0].username,
});
console.log("nnnnnnnnnuuuu",userle)
console.log('nnnnnnnnnnn',userL[0])
const handleSelectChangeL = (object, action) => {
setIndex(userL[object.id]);
setUserlevel(null);
console.log("select check", object.label, action.name, index);
let name = action.name;
let value = object.value;
setFormData((prevFormData) => ({
...prevFormData,
[name]: value,
}));
};
<Col lg={2}>
<label for="user">
<header>User</header>
</label>
<Select
options={userL.map((data, index) => ({
value: data.username,
label: data.username,
id: index,
}))}
styles={styles2}
value={userle}
name="user"
onChange={handleSelectChangeL}
placeholder="User"
theme={(theme) => ({
...theme,
colors: {
...theme.colors,
text: "black",
primary25: "#d6fdf7",
primary: "#0bb7a7",
primary50: "#d6fdf7",
},
})}
></Select>
</Col>
If you want to set the state of the userle from the first element of the data, do it this way.
const [userle, setUserle] = useState()
const [userL, setUserL] = useState([]);
useEffect(() => {
axios
.get(`${DJANGO_SERVER_ADDRESS}/auth/analyst/`)
.then((res) => {
setUserL(res.data);
setUserle(res.data[0]?.username);
})
.then(
(result) => {
},
(error) => {
}
);
}, []);
EDIT
To also update the userle state onChange of the dropdown list, add setUserle() under your handleSelectChangeL function.
JS
const handleSelectChangeL = (object, action) => {
setIndex(userL[object.id]);
setUserlevel(null);
console.log("select check", object.label, action.name, index);
let name = action.name;
let value = object.value;
setUserle(value); // Add this line
setFormData((prevFormData) => ({
...prevFormData,
[name]: value,
}));
};
if you using useState, You cannot get expected result of updated state in same function, but if want to print the result, you must write useEffect with dependency userle state or maybel you can console it above "return" statement in functional Commponent

Updating the state of object values in React

I have an array set in state like:
const Theme = {
name: "theme",
roots: {
theme: Theme,
},
state: {
theme: {
quiz: {
quizGender: null,
quizSleepComfort: {
justMe: {
soft: null,
medium: null,
firm: null,
},
partner: {
soft: null,
medium: null,
firm: null,
}
},
},
},
},
actions: {
// ...
},
};
I then have a component that has checkboxes, one for soft, medium, and firm. The code for the component is:
const Question = ({ state }) => {
const [checkedItems, setCheckedItems] = useState([]);
const checkboxes = [
{
label: "Firm",
value: "firm",
},
{
label: "Medium",
value: "medium",
},
{
label: "Soft",
value: "soft",
},
];
state.theme.quiz.quizSleepComfort.justMe = checkedItems;
return (
<QuestionCommonContainer>
{checkboxes.map((item, id) => (
<QuizCheckbox
label={item.label}
name={item.label}
value={item.value}
selected={checkedItems[item.value] === true}
onChange={(e) => {
setCheckedItems({
...checkedItems,
[e.target.value]: e.target.checked,
});
}}
/>
))}
</QuestionCommonContainer>
);
};
export default connect(Question);
This specific component is just interacting with state.theme.quiz.quizSleepComfort.justMe object, not the partner object.
As of right now when a checkbox is selected, let's say the checkbox for "firm" is checked, the state gets updated to what looks like this:
...
quizSleepComfort: {
justMe: {
firm: true,
},
partner: {
soft: null,
medium: null,
firm: null,
}
},
...
I am trying to figure out how I would be able to alter this components code so that instead of setting the justMe object to include only the items that are checked (in this case "firm"), it should keep the other items as well ("soft", "medium") as null.
Please let me know if there is more info i should provide.
Okay. So the following is bad practice
state.theme.quiz.quizSleepComfort.justMe = checkedItems;
You should pass a function to the Question component, something like onChange.
The onChange function should update the state in your parent component. Use the spread operator ... to get a copy of the old object. for example
const onChange = (newState) =>
setState((oldState) => ({
...oldState,
justMe: { ...oldState.justMe, ...newState },
}));
the resulting object will contain all the properties of the original state but will overwrite any property set on newState in justMe. If the property that you want to update is more nested, just repeat the steps of spreading.
--- UPDATE ---
I have added an example that I think is close to what you are trying to achieve.
const Parent = () => {
const [state, setState] = useState(initialState);
const onChange = useCallback(
(newState) =>
setState((oldState) => ({
...oldState,
theme: {
...oldState.theme,
quiz: {
...oldState.theme.quiz,
quizSleepComfort: {
...oldState.theme.quizSleepComfort,
justMe: {
...oldState.theme.quizSleepComfort.justMe,
...newState,
},,
},
},
},
})),
[],
);
return <Question onChange={onChange} />;
};
const checkboxes = [
{
label: 'Firm',
value: 'firm',
},
{
label: 'Medium',
value: 'medium',
},
{
label: 'Soft',
value: 'soft',
},
];
const Question = ({ onChange }) => {
const [checkedItems, setCheckedItems] = useState([]);
useEffect(() => {
onChange(checkedItems);
}, [checkedItems, onChange]);
return (
<QuestionCommonContainer>
{checkboxes.map((item, id) => (
<QuizCheckbox
label={item.label}
name={item.label}
value={item.value}
selected={checkedItems[item.value] === true}
onChange={(e) => {
setCheckedItems((oldCheckedItems) => ({
...oldCheckedItems,
[e.target.value]: e.target.checked,
}));
}}
/>
))}
</QuestionCommonContainer>
);
};
export default connect(Question);
As you are having a really nested object to update, it might be worth taking a look at Object.assign

Material Table not updating table data after mutation

When a user adds additional information, a mutation is made to the database adding the new info, then the local state is updated, adding the new information to the lead.
My mutation and state seem to get updated fine, the issue seems to be that the state of the Material Table component does not match its 'data' prop. I can see in the React Dev tools that the state was updated in the parent component and is being passes down, the table just seems to be using stale data until I manually refresh the page.
I will attach images of the React Devtools as well as some code snippets. Any help would be much appreciated.
Devtools Material Table data prop:
Devtools Material Table State
Material Table Parent Component:
const Leads = () => {
const [leadState, setLeadState] = useState({});
const [userLeadsLoaded, setUserLeadsLoaded] = React.useState(false);
const [userLeads, setUserLeads] = React.useState([]);
const { isAuthenticated, user, loading } = useAuth()
const [
createLead,
{ data,
// loading: mutationLoading,
error: mutationError },
] = useMutation(GQL_MUTATION_CREATE_LEAD);
const params = { id: isAuthenticated ? user.id : null };
const {
loading: apolloLoading,
error: apolloError,
data: apolloData,
} = useQuery(GQL_QUERY_ALL_LEADS, {
variables: params,
});
useEffect(() => {
if (apolloData) {
if (!userLeadsLoaded) {
const { leads } = apolloData;
const editable = leads.map(o => ({ ...o }));
setUserLeads(editable);
setUserLeadsLoaded(true);
};
}
}, [apolloData])
if (apolloLoading) {
return (
<>
<CircularProgress variant="indeterminate" />
</>
);
};
if (apolloError) {
console.log(apolloError)
//TODO: Do something with the error, ie default user?
return (
<div>
<div>Oh no, there was a problem. Try refreshing the app.</div>
<pre>{apolloError.message}</pre>
</div>
);
};
return (
<>
<Layout leadState={leadState} setLeads={setUserLeads} leads={userLeads} setLeadState={setLeadState} createLead={createLead}>
{apolloLoading ? (<CircularProgress variant="indeterminate" />) : (<LeadsTable leads={userLeads} setLeads={setUserLeads} />)}
</Layout>
</>
)
}
export default Leads
Handle Submit function for adding additional information:
const handleSubmit = async (event) => {
event.preventDefault();
const updatedLead = {
id: leadState.id,
first_name: leadState.firstName,
last_name: leadState.lastName,
email_one: leadState.email,
address_one: leadState.addressOne,
address_two: leadState.addressTwo,
city: leadState.city,
state_abbr: leadState.state,
zip: leadState.zipCode,
phone_cell: leadState.phone,
suffix: suffix,
address_verified: true
}
const { data } = await updateLead({
variables: updatedLead,
refetchQueries: [{ query: GQL_QUERY_GET_USERS_LEADS, variables: { id: user.id } }]
})
const newLeads = updateIndexById(leads, data.updateLead)
console.log('New leads before setLeads: ', newLeads)
setLeads(newLeads)
// setSelectedRow(data.updateLead)
handleClose()
};
Material Table Component:
const columnDetails = [
{ title: 'First Name', field: 'first_name' },
{ title: 'Last Name', field: 'last_name' },
{ title: 'Phone Cell', field: 'phone_cell' },
{ title: 'Email', field: 'email_one' },
{ title: 'Stage', field: 'stage', lookup: { New: 'New', Working: 'Working', Converted: 'Converted' } },
{ title: 'Active', field: 'active', lookup: { Active: 'Active' } },
];
const LeadsTable = ({ leads, setLeads }) => {
const classes = useStyles();
const { user } = useAuth();
const [isLeadDrawerOpen, setIsLeadDrawerOpen] = React.useState(false);
const [selectedRow, setSelectedRow] = React.useState({});
const columns = React.useMemo(() => columnDetails);
const handleClose = () => {
setIsLeadDrawerOpen(!isLeadDrawerOpen);
}
console.log('All leads from leads table render: ', leads)
return (
<>
<MaterialTable
title='Leads'
columns={columns}
data={leads}
icons={tableIcons}
options={{
exportButton: false,
hover: true,
pageSize: 10,
pageSizeOptions: [10, 20, 30, 50, 100],
}}
onRowClick={(event, row) => {
console.log('Selected Row:', row)
setSelectedRow(row);
setIsLeadDrawerOpen(true);
}}
style={{
padding: 20,
}}
/>
<Drawer
variant="temporary"
open={isLeadDrawerOpen}
anchor="right"
onClose={handleClose}
className={classes.drawer}
>
<LeadDrawer onCancel={handleClose} lead={selectedRow} setLeads={setLeads} setSelectedRow={setSelectedRow} leads={leads} />
</Drawer>
</>
);
};
export default LeadsTable;
Try creating an object that contains refetchQueries and awaitRefetchQueries: true. Pass that object to useMutation hook as a 2nd parameter. See example below:
const [
createLead,
{ data,
loading: mutationLoading,
error: mutationError },
] = useMutation(GQL_MUTATION_CREATE_LEAD, {
refetchQueries: [{ query: GQL_QUERY_GET_USERS_LEADS, variables: { id: user.id } }],
awaitRefetchQueries: true,
});
Manually updating cache. Example blow is adding a new todo. In your case you can find and update the record before writing the query.
const updateCache = (cache, {data}) => {
// Fetch the todos from the cache
const existingTodos = cache.readQuery({
query: GET_MY_TODOS
});
// Add the new todo to the cache (or find and update an existing record here)
const newTodo = data.insert_todos.returning[0];
cache.writeQuery({
query: GET_MY_TODOS,
data: {todos: [newTodo, ...existingTodos.todos]}
});
};
const [addTodo] = useMutation(ADD_TODO, {update: updateCache});

How to update state array?

I am making a App in Reactjs. In it I have an API that gets data from a AWS database table and populates a state array which then populates a tables.
I cant figure out how to let a user in turn update the values of the table and then save those changes and let the API upload it back into the data base.
I have a update method but it gives an error saying that I cant update the state Array or would complain that the column array is undefined.
I have the following code:
export default function Complaints(props) {
const [complaint, setComplaint] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [isInEditMode, setIsEditMode] = useState(false);
const [defaultValue, setDefaultValue] = useState("");
var columsArr = [
{ title: 'Customer ID', field: 'id' },
{ title: 'Issue', field: 'complaintName' },
{ title: 'Description', field: 'complaintDescription'},
{ title: 'Order ID', field: 'complaintOrderId'},
{ title: 'Submitted', field: 'createdAt'},
{ title: 'Updated', field: 'updatedAt'},
{ title: 'Admin Comment', field: 'adminComment'},
];
useEffect(() => {
async function onLoad() {
if (!props.isAuthenticated) {
return;
}
try {
const complaint = await loadComplaint();
setComplaint(complaint);
setState({
columns: [state.columns, ...columsArr],
complaint: [...state.complaint, ...complaint]
});
console.log(complaint)
} catch (e) {
alert(e);
}
setIsLoading(false);
}
onLoad();
}, [props.isAuthenticated]);
function loadComplaint() {
return API.get("kleen", "/Complaint");
}
// function edit(adminComment) {
// setIsEditMode(true);
// setDefaultValue(adminComment);
// console.log("value is"+ adminComment);
// }
// function updateComplaint() {
// return API.put("kleen", `/Complaint/${props.}`);
// }
const [state, setState] = React.useState({
columns: [],
complaint: []
});
return (
<MaterialTable style={{
marginTop: "8rem",
marginLeft: "auto",
marginRight: "auto",
position: "sticky",
}}
title="Complaints"
columns={state.columns}
data={state.complaint}
editable={{
onRowUpdate: (newData, oldData) =>
new Promise(resolve => {
setTimeout(() => {
resolve();
if (oldData) {
let key = 1;
setState(prevState => {
complaint: prevState.complaint.map(el => el.key === key ? {...el, adminComment: "testing"}: el)
// const data = [...prevState.data];
// data[data.indexOf(oldData)] = newData;
// return { ...prevState, data };
});
}
}, 600);
}),
}}
/>
);
}
I am very new to Reactjs, any help would be greatly appreciated.
One way that you can achieve is by:
First store the state in a variable
Update the array variable
Restore the variable to the state.
If you want to use state then better to use component class
export default class Complaints extends Component {
//assign initial value to state
state={
propsObj : this.props
}
...
}
If you want to use component you have to import it
import React, { Component } from "react";
inside component class you have to use this.props instead of props
later if you want to change state just use this.setState({ propsObj: 'some thing new'});

Categories