Why this setState caused infinite loop? - javascript

I created a react component with rendering call details, in component I use useEffect to set the callInfo state, then it caused infinite loop, even I use [] as second parameter, can anyone help me fix this, thanks!
import { useLocation } from "react-router-dom";
import { useState, useEffect } from "react";
const ActivityDetail = ({ onToggleArchived }) => {
const { call } = useLocation().state;
const [callInfo, setCallInfo] = useState(null);
console.log({...call});
useEffect(() => {
setCallInfo({ ...call });
}, [])
return (
<div>
<h3 className="title">Call Details</h3>
<hr />
{
callInfo && <div>
<p>From: {callInfo.from}</p>
<p>To: {callInfo.to}</p>
<p>Time: {callInfo.created_at}</p>
<button onClick={onToggleArchived(callInfo.id)}>
{callInfo.is_archived ? "Unarchive" : "Archive"}
</button>
</div>
}
</div>
)
}
export default ActivityDetail
This is error information:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

The problem lies within your return:
<button onClick={onToggleArchived(callInfo.id)}>
{callInfo.is_archived ? "Unarchive" : "Archive"}
</button>
Here, you are calling the function onToggleArchived which presumably (it's not in the code you posted) does state updates.
how to fix it:
wrap it in an arrow function
<button onClick={()=>onToggleArchived(callInfo.id)}>
{callInfo.is_archived ? "Unarchive" : "Archive"}
</button>

EDIT: In addition to the original answer about misusing state (which you need to correct), I missed the point that you were calling the function instead of wrapping it:
<button onClick={() => onToggleArchived(callInfo.id)}>
// instead of
<button onClick={onToggleArchived(callInfo.id)}>
ORIGINAL ANSWER
in component I use useEffect to set the callInfo state
But this is a problem because call is not component state - it's coming from useLocation(). Just let it come from there and remove the component state stuff altogether.
i.e. treat it as if it were a prop.
import { useLocation } from "react-router-dom";
import { useState, useEffect } from "react";
const ActivityDetail = ({ onToggleArchived }) => {
const { call: callInfo } = useLocation().state;
return (
<div>
<h3 className="title">Call Details</h3>
<hr />
{
callInfo && <div>
<p>From: {callInfo.from}</p>
<p>To: {callInfo.to}</p>
<p>Time: {callInfo.created_at}</p>
<button onClick={() => onToggleArchived(callInfo.id)}>
{callInfo.is_archived ? "Unarchive" : "Archive"}
</button>
</div>
}
</div>
)
}
export default ActivityDetail

Related

React updating useState array not causing element refresh

I am unable to trigger the component to update when the viaList state is changed. If anyone could help, please <3
I don't want to append these children if possible.
import React, { useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
function BookingForm() {
const [viaList, setViaList] = useState([]);
function addViaLocation() {
const arr = viaList;
arr.push(
<li key={uuidv4()}>
<label className="form-label">
Via Location
</label>
</li>)
setViaList(arr);
}
return (
<>
<button onClick={addViaLocation} className="button-standard primary" type="button">
Add Additional Location
</button>
<ul>{viaList.length > 0 && viaList.map(item => item)}</ul>
</>
);
}
export default BookingForm;
Separating the functionality into a separate component
Triggering the update using props
Regular DOM stuff (appending doesn't fit our use case)
You need to create a new array such that React understands it has changed. If you only push to the old viaList it will still hold that viaList === previousViaList. Instead create a new array, e.g. with the spread operator:
function addViaLocation() {
const newElement = (
<li key={uuidv4()}>
<label className="form-label">
Via Location
</label>
</li>)
setViaList([...viaList, newElement]);
}
Fixed it...
I was doing the following (just longer):
setViaList([...viaList, <li>STUFF</li>])
I had to update the state array using a callback instead:
setViaList(viaList => [...viaList, <li>STUFF</li>])
import React, { useEffect, useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
function BookingForm() {
const [viaList, setViaList] = useState([]);
function addViaLocation() {
const arr = [...viaList];
arr.push(
<li key={uuidv4()}>
<label className="form-label">
Via Location
</label>
</li>)
setViaList(arr);
}
return (
<>
<button onClick={addViaLocation} className="button-standard primary" type="button">
Add Additional Location
</button>
<ul>{viaList.length > 0 && viaList.map(item => item)}</ul>
</>
);
}
export default BookingForm;
You can Create a shallow Copy of the Array by spreading the current state array and then pushing the new data and update state with the shallow copy

React state not reactive when triggered from outside library

React beginner here.
My code 👇
import Head from 'next/head';
import styles from '../styles/Home.module.css';
import { useState, useEffect } from 'react';
import Mousetrap from 'mousetrap';
export default function Home() {
const [count, setCount] = useState(0);
const triggerSomething = () => {
console.log(count);
};
useEffect(() => {
Mousetrap.bind(['ctrl+s', 'command+s'], e => {
e.preventDefault();
triggerSomething();
});
return () => {
Mousetrap.unbind(['ctrl+s', 'command+s']);
};
}, []);
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>count: {count}</h1>
<p className={styles.description}>
<button onClick={() => setCount(count + 1)}>increment</button>
<br />
<br />
<button onClick={triggerSomething}>triggerSomething</button>
</p>
</main>
</div>
);
}
I'm having an issue when trying to trigger an event from Mousetrap. The count variable is not reactive when triggered from mousetrap but reactive when triggered from the button with onClick.
To replicate this bug you need to:
click the increment button once
click the triggerSomething button. The console should print out 1 (the current state of count)
push command+s or ctrl+s to trigger the same method. The console prints out 0 (the state of count when the component loaded). That should print 1 (the current state).
What am I doing wrong? What pattern should I use here?
UPDATE:
Stackblitz here
When you change the state, the component is re-rendered, i.e. the function is executed again, but the useState hook returns the updated counter this time. To use this updated value in your MouseTrap, you must create a new handler (and remove the old one). To achieve this, simply remove the dependency array of your useEffect call. It will then use the newly created triggerSomething function.
import Head from 'next/head';
import styles from '../styles/Home.module.css';
import { useState, useEffect } from 'react';
import Mousetrap from 'mousetrap';
export default function Home() {
const [count, setCount] = useState(0);
const triggerSomething = () => {
console.log(count);
};
useEffect(() => {
Mousetrap.bind(['ctrl+s', 'command+s'], e => {
e.preventDefault();
triggerSomething();
});
return () => {
Mousetrap.unbind(['ctrl+s', 'command+s']);
};
}); // Notice that I removed the dependency array
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>count: {count}</h1>
<p className={styles.description}>
<button onClick={() => setCount(count + 1)}>increment</button>
<br />
<br />
<button onClick={triggerSomething}>triggerSomething</button>
</p>
</main>
</div>
);
}
In the triggerSomething method you can use setCount(count => count + 1) and this should work.
The issue you have is when you don't put the triggerSomething as a dep in the useEffect, count would be the same as the initial state which is 0. but when passing a function counting on the previous value to setCount, you'll spare this issue.

React / Functional component / Conditional render on callback / Not Working

Why this does not work ?
import React from 'react';
function Room() {
let check = null;
const ibegyouwork = () => {
check = <button>New button</button>;
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
And this works fine ?
import React from 'react';
function Room() {
let check = null;
return (
<div>
<button>No need for this button because in this case the second button is auto-displayed</button>
{check}
</div>
);
}
export default Room;
Basically I try to render a component based on a condition. This is a very basic example. But what I have is very similar. If you wonder why I need to update the check variable inside that function is because in my example I have a callback function there where I receive an ID which I need to use in that new component.
The example that I provided to you is basically a button and I want to show another one when I press on this one.
I am new to React and despite I searched in the past 2 hours for a solution I couldn't find anything to address this issue.
Any tips are highly appreciated !
Your component has no idea that something has changed when you click the button. You will need to use state in order to inform React that a rerender is required:
import React, {useState} from 'react'
function Room() {
const [check, setCheck] = useState(null);
const ibegyouwork = () => {
setCheck(<button>New button</button>);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{check}
</div>
);
}
export default Room;
When you call setCheck, React basically decides that a rerender is required, and updates the view.
The latter is working because there are no changes to the check value that should appear on the DOM.
If check changes should impact and trigger the React render function, you would want to use a state for show/hide condition.
import React from 'react';
const Check = () => <button>New button</button>;
function Room() {
const [show, setShow] = React.useState(false);
const ibegyouwork = () => {
setShow(true);
}
return (
<div>
<button onClick={ibegyouwork}>Display my button now !!!!</button>
{show && <Check />}
</div>
);
}
export default Room;

Delete button without { Component } in ReactJs

This is surely really easy but I didn't find the solution.. I want to use the delete button but I'm getting an error. Thanks in advance guys.
This is the code where props call the properties of a Toy class parent, I want to delete by Id using MongoDB:
import React, { useContext } from 'react';
import UserContext from '../../context/UserContext';
import Axios from 'axios';
export default function AdminOptions(props) {
const { userData } = useContext(UserContext);
console.log(props.value._id)
//I tried this log and it gives me the id of the toy
deleteToy = async(id) => {
await Axios.delete('api/toys/delete' + id);
alert('Toy deleted');
}
return (
<div>
{userData.user ? (
<>
<br/>
<button className="btn btn-danger" onClick={this.deleteToy(props.value._id)}>
Delete toy
</button>
</>
) : (
<>
</>
)}
</div>
)
}
And this is the error I get
Failed to compile.
Line 15:5: 'deleteToy' is not defined no-undef
You are using the variable deleteToy without defining it (with either const, let or var), hence the error.
You are referring to this.deleteToy, yet your variable is just deleteToy.
In your event handler, you are not passing a function reference but actually calling the function right away. You can prepend () => to fix it (passing an arrow function that then calls yours when called). (Thanks, Emre Koc, I missed that.)
The fixed code would look like this:
import React, { useContext } from 'react';
import UserContext from '../../context/UserContext';
import Axios from 'axios';
export default function AdminOptions(props) {
const { userData } = useContext(UserContext);
console.log(props.value._id)
//I tried this log and it gives me the id of the toy
const deleteToy = async(id) => {
await Axios.delete('api/toys/delete' + id);
alert('Toy deleted');
}
return (
<div>
{userData.user ? (
<>
<br/>
<button className="btn btn-danger" onClick={() => deleteToy(props.value._id)}>
Delete toy
</button>
</>
) : null}
</div>
)
}
Add const before deleteToy . It will work

Why I get props is undefined?

import React from "react";
import styles from "../articles.css";
const TeamInfo = props => (
<div className={styles.articleTeamHeader}>
<div className={styles.left}>
style={{
background: `url('/images/teams/${props.team.logo}')`
}}
</div>
<div className={styles.right}>
<div>
<span>
{props.team.city} {props.team.name}
</span>
</div>
<div>
<strong>
W{props.team.stats[0].wins}-L{props.team.stats[0].defeats}
</strong>
</div>
</div>
</div>
);
export default TeamInfo;
the code that render this
import React from 'react';
import TeamInfo from '../../Elements/TeamInfo';
const header = (props) => {
const teaminfofunc = (team) => {
return team ? (
<TeamInfo team={team}/>
) : null
}
return (
<div>
{teaminfofunc(props.teamdata)}
</div>
)
}
export default header;
and I am getting error TypeError: props is undefined in line 8 why is that ?
Line 8 is
background: url('/images/teams/${props.team.logo}')
Update:
I found that in index.js the componentWillMount bring the data correctly but in the render() those data (article and team) was not passed to render, any idea why ?
import React, {Component} from 'react';
import axios from 'axios';
import {URL} from "../../../../config";
import styles from '../../articles.css';
import Header from './header';
import Body from './body';
class NewsArticles extends Component {
state = {
article:[],
team: []
}
componentWillMount() {
axios.get(`${URL}/articles?id=${this.props.match.params.id}`)
.then(response => {
let article = response.data[0];
axios.get(`${URL}/teams?id=${article.team}`)
.then(response => {
this.props.setState({
article,
team:response.data
})
})
})
}
render() {
const article = this.state.article;
const team = this.state.team;
return (
<div className={styles.articleWrapper}>
<Header teamdata={team[0]} date={article.date} author={article.author} />
<Body />
</div>
)
}
}
export default NewsArticles;
You render your component immediately, long before your AJAX call finishes, and pass it the first element of an empty array:
<Header teamdata={team[0]}
componentWillMount does not block rendering. In your render function, short circuit if there's no team to render.
render() {
const { article, team, } = this.state;
if(!team || !team.length) {
// You can return a loading indicator, or null here to show nothing
return (<div>loading</div>);
}
return (
<div className={styles.articleWrapper}>
<Header teamdata={team[0]} date={article.date} author={article.author} />
<Body />
</div>
)
}
You're also calling this.props.setState, which is probably erroring, and you should never call setState on a different component in React. You probably want this.setState
You should always gate any object traversal in case the component renders without the data.
{props && props.team && props.team.logo ? <div className={styles.left}>
style={{
background: `url('/images/teams/${props.team.logo}')`
}}
</div> : null}
This may not be you exact issue, but without knowing how the prop is rendered that is all we can do from this side of the code.
Update based on your edit. You can't be sure that props.teamdata exists, and therefore your component will be rendered without this data. You'll need to gate this side also, and you don't need to seperate it as a function, also. Here is an example of what it could look like:
import React from 'react';
import TeamInfo from '../../Elements/TeamInfo';
const header = (props) => (
<div>
{props.teamdata ? <TeamInfo team={props.teamdata}/> : null}
</div>
)
export default header;
First -- while this is stylistic -- it's not good practice to pass props directly to your functional component. Do this instead.
const TeamInfo = ({team}) => (
<div className={styles.articleTeamHeader}>
<div className={styles.left}>
style={{
background: `url('/images/teams/${team.logo}')`
}}
</div>
<div className={styles.right}>
<div>
<span>
{team.city} {team.name}
</span>
</div>
<div>
<strong>
W{team.stats[0].wins}-L{team.stats[0].defeats}
</strong>
</div>
</div>
</div>
);
Second, you might just want to do some kind of null check. If team is undefined the first time the component tries to render, you might just want to render null so you're not wasting cycles.
In case this isn't the issue, you'd learn a lot by console.log-ing your props so you know what everything is each time your component tries to render. It's okay if data is undefined if you're in a state that will soon resolve.

Categories

Resources