Why is componentDidMount stuck in infinite loop? - reactjs

I'm trying to update the comment section in my app.
First in componentDidMount I fetch and list if there are comments. I'm sending post request when submitting new comment and then re-render my component and show all possible comments.
When updating component I'm calling componentDidUpdate, passing an if statement prevState.commentHistory !== this.state.commentHistory and after I'm trying to get the list of all comments again. That is when my app goes into infinite loop.
Any help would be appreciated.
class CommentComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
commentText: '',
commentHistory: [],
};
}
componentDidMount = () => {
this.getMethod()
}
componentDidUpdate = (prevProps, prevState) => {
if (prevState.commentHistory !== this.state.commentHistory) {
this.getMethod()
}
}
getMethod = () => {
requestConsoleAPI('GET', 'something/' + getDetailPageId() + '/comments', null, (response) => {
this.setState({ commentHistory: response})
})
}
handleChange = (e) => {
e.preventDefault();
this.setState({
[e.target.name]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
let data = {
comment: this.state.commentText
}
if (data.comment) {
requestConsoleAPI('POST', 'something/' + getDetailPageId() +'/comments', data, (response) => {
this.setState({ commentHistory: [response] })
})
}
this.setState({
commentText: '',
})
}
render() {
let renderComments = this.state.commentHistory.map((singleComment) => {
return (
<tr data-id={singleComment.id} key={singleComment.id}>
<td>{singleComment.createdAt}</td>
<td>{singleComment.author}</td>
<td>{singleComment.text}</td>
</tr>
)
});
return (
<div className="change-wrapper hidden" id="comments">
<div className="change-head">
<h3 data-count="X">Comments</h3>
</div>
<div className="change-body">
<form className="form" onSubmit={this.handleSubmit}>
<div className="row">
<div className="col-lg-12">
<div className="form-group">
<textarea name="commentText" value={this.state.commentText} onChange={this.handleChange} className="form-control" placeholder="Your comment..." rows="8"></textarea>
</div>
<div className="form-group m-t-25">
<button type="submit" className="btn btn-next" >Send Comment</button>
</div>
</div>
</div>
</form>
<div className="row">
<div className="list col-lg-12">
<h4 className="m-t-50">Comment History</h4>
<table className="table table-striped table-comments">
<thead>
<tr>
<th>Created</th>
<th>Author</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{ renderComments }
</tbody>
<tfoot>
<tr>
<td colSpan="3" className="text-center"><i>No comments yet</i></td>
</tr>
</tfoot>
</table>
{/* {{ consoleMacros.listLoading() }} */}
</div>
</div>
</div>
</div>
);
}
}
const domContainer = document.querySelector('#comment_container');
ReactDOM.render(<CommentComponent/>, domContainer);

As stated by El Aoutar Hamza, you're comparing the arrays reference instead of checking its values. If you want to compare two arrays, you'll have to check each value between them. But, as far as I understand, for your case, you can simply compare the array's length, this way your UI will update everytime some comment is added/removed

Related

Modify the state with other component for the input in React

I carry out a project which can modify the price of a product (recovered from a fake API) and then at the click of a button carries out the update by calculating the VAT of 20%. I encounter a problem I would like to have a price state and that in this state it's the value of my input namely {listProduct.price} but it doesn't work.
If you have solutions, I am interested, thank you in advance. (sorry I'm new to React I still have a bit of trouble with all these concepts)
import React, { Component } from 'react'
import '../css/ProductsDetails.css'
import {AiOutlineArrowLeft} from "react-icons/ai";
import {Link} from 'react-router-dom'
export default class ProductsDetails extends Component {
state = {
id: this.props.match.params.id,
price:
}
updatePrice = (e) => {
console.log(e);
this.setState({
price: e.target.value
})
}
render() {
const {location: {state: {listProduct}}} = this.props;
return (
<div className="products__details">
<Link to="/"><AiOutlineArrowLeft className="nav__arrow" /></Link>
<h1 className="details__title">{listProduct.title}</h1>
<div className="details__align--desk">
<div className="details__img">
<img className="product__img" src={listProduct.image} alt="Affichage du produit"/>
</div>
<div className="products__align--desk">
<h2 className="product__title">Description</h2>
<p className="product__description">{listProduct.description}</p>
<h2 className="product__title">Price</h2>
<form className="form__price">
<input className="input__price" type="text" value={listProduct.price} onChange={this.updatePrice} />
<p>Price (including VAT): {Math.round((listProduct.price + listProduct.price * 0.2)*100) /100} €</p>
<br/>
<input className="btn__update" type="submit" value="Update product" />
</form>
</div>
<div className="category__align--desk">
<h2 className="product__title">Category</h2>
<p className="product__category">{listProduct.category}</p>
</div>
</div>
</div>
)
}
}
export default class Products extends Component {
constructor(props) {
super(props);
this.state = {productsData: []};
}
componentDidMount = () => {
axios.get('https://fakestoreapi.com/products?limit=7')
.then(res => {
console.log(res.data)
this.setState ({
productsData: res.data
})
})
}
render() {
const listsProducts = this.state.productsData.map(listProduct => {
return <tbody className="products__body">
<tr>
<td> <Link to={{pathname: "/products-details/" + listProduct.id,state: {listProduct}}}>{listProduct.title}</Link></td>
<td className="products__category">{listProduct.category}</td>
<td>{listProduct.price}</td>
<td>{Math.round((listProduct.price + listProduct.price * 0.2)*100) /100}</td>
</tr>
</tbody>
})
return (
<main className="products">
<h1 className="products__title">Products management</h1>
<table cellSpacing="0">
<thead className="products__head">
<tr>
<th className="table--title">Product name</th>
<th className="table--title">Category</th>
<th className="table--title">Price</th>
<th className="table--title">Price (including VAT)</th>
</tr>
</thead>
{listsProducts}
</table>
</main>
)
}
}
Inside a react component:
1 - You declare the initial state of your component, which is, in this case, the price that the product has before the user writes something. For now, we'll set it to 0:
state = {
id: this.props.match.params.id,
price: this.props.listProduct.price ? this.props.listProduct.price : 0
}
2 - Then, in the render method, we access the price value from this.state
3 - Finally, we modify our input element so that it gets the value of the price.
<input className="input__price" type="text" value={price} onChange={this.updatePrice} />
The rest of the component was working well.
This is the result:
import React, { Component } from 'react'
import '../css/ProductsDetails.css'
import {AiOutlineArrowLeft} from "react-icons/ai";
import {Link} from 'react-router-dom'
export default class ProductsDetails extends Component {
state = {
id: this.props.match.params.id,
price: '0'
}
updatePrice = (e) => {
console.log(e);
this.setState({
price: e.target.value
})
}
render() {
const {price} = this.state
return (
<div className="products__details">
<Link to="/"><AiOutlineArrowLeft className="nav__arrow" /></Link>
<h1 className="details__title">{listProduct.title}</h1>
<div className="details__align--desk">
<div className="details__img">
<img className="product__img" src={listProduct.image} alt="Affichage du produit"/>
</div>
<div className="products__align--desk">
<h2 className="product__title">Description</h2>
<p className="product__description">{listProduct.description}</p>
<h2 className="product__title">Price</h2>
<form className="form__price">
<input className="input__price" type="text" value={price} onChange={this.updatePrice} />
<p>Price (including VAT): {Math.round((listProduct.price + listProduct.price * 0.2)*100) /100} €</p>
<br/>
<input className="btn__update" type="submit" value="Update product" />
</form>
</div>
<div className="category__align--desk">
<h2 className="product__title">Category</h2>
<p className="product__category">{listProduct.category}</p>
</div>
</div>
</div>
)
}
}
Start off with the price at 0 (not in quotes) in state, and then...
const price = this.state.price || (this.props.listProduct ? this.props.listProduct.price : 0)
<input className="input__price" type="text" value={price} onChange{this.updatePrice} />
So if the state value has been updated, that will be used, if not it will check if the price is available in props and use that, and if not it will display zero.

How to map JSON data as a table in React

I'm trying to display data after fetching it, but that does not work :
import React, { Component } from "react";
import { Table, Button, ButtonToolbar } from "react-bootstrap";
const URL = "http://localhost:51644/api/";
let passedAthleteId = 0;
let passedAthleteSessionId = 0;
class AthleteTrainingSession extends Component {
constructor(props) {
super(props);
this.state = {
athleteTrainingSession: [],
discussions: [],
warmups: [],
workouts: [],
stretchings: [],
};
}
componentDidMount() {
this.fetchAthleteTrainingSession();
}
componentDidUpdate() {
this.fetchAthleteTrainingSession();
}
fetchAthleteTrainingSession = () => {
fetch(URL + `Coaches/4/Athletes/1/AthleteSessions/4`)
.then((response) => response.json())
.then((data) => {
this.setState({
athleteTrainingSession: data,
});
});
};
render() {
const {
athleteTrainingSession,
discussions,
warmups,
workouts,
stretchings,
} = this.state;
passedAthleteId = this.props.match.params.athleteId;
passedAthleteSessionId = this.props.match.params.athleteSessionId;
this.discussions = this.state.athleteTrainingSession.Discussions;
this.warmups = this.state.athleteTrainingSession.Warmups;
this.workouts = this.state.athleteTrainingSession.Workouts;
this.stretchings = this.state.athleteTrainingSession.Stretchings;
console.log(athleteTrainingSession);
console.log(this.warmups);
return (
<React.Fragment>
<div>
<h2 className="mt-2">
Programme d'entraînement :{" "}
{athleteTrainingSession.TrainingProgramName}
</h2>
<h4>
Séance d'entraînement : {athleteTrainingSession.TrainingSessionName}
</h4>
</div>
<div>
<ButtonToolbar>
<Button variant="primary">Ajouter</Button>
<Button variant="secondary">Discussion</Button>
</ButtonToolbar>
<h4>Échauffement</h4>
<Table className="mt-4" striped bordered hover size="sm">
<thead>
<tr className="d-flex">
<th className="col-6">Exercice</th>
<th className="col-6">Options</th>
</tr>
</thead>
<tbody>
{warmups.map((warm) => (
<tr className="d-flex" key={warm}>
<td className="col-6">{warm.ExerciseName}</td>
<td className="col-6">
<ButtonToolbar>
<Button className="mr-2" variant="info">
Modifier
</Button>
<Button className="mr-2" variant="danger">
Supprimer
</Button>
</ButtonToolbar>
</td>
</tr>
))}
</tbody>
</Table>
</div>
</React.Fragment>
);
}
}
export default AthleteTrainingSession;
athleteTrainingSession contains the fetched data, and warmups is a sub-object for athleteTrainingSession.
When I console.log(warmups), I can see that it does contain data, but I cannot display it in the table.
athleteTrainingSession contains the fetched data, and warmups is a sub-object for athleteTrainingSession.
When I console.log(warmups), I can see that it does contain data, but I cannot display it in the table.
I think you have misconception of using state in component.
You're able to console the warmups because in your code you console.log(this.warmups), but you render the map with this.state.warmups
you should setState all of the data that you get from fetch, i.e:
fetchAthleteTrainingSession = () => {
fetch(URL + `Coaches/4/Athletes/1/AthleteSessions/4`)
.then((response) => response.json())
.then((data) => {
this.setState({
athleteTrainingSession: data,
warmups: data.Warmups,
workouts: data.Workouts,
discussions: data.Discussions,
stretchings: data.Stretchings,
});
});
};
by doing this way, now you can access the warmups data from this.state.warmups then render it
render() {
const {
athleteTrainingSession,
discussions,
warmups,
workouts,
stretchings,
} = this.state;
return (
<React.Fragment>
...
{warmups.map((warm) => (
<tr className="d-flex" key={warm}>
<td className="col-6">{warm.ExerciseName}</td>
<td className="col-6">
<ButtonToolbar>
<Button className="mr-2" variant="info">
Modifier
</Button>
<Button className="mr-2" variant="danger">
Supprimer
</Button>
</ButtonToolbar>
</td>
</tr>
))}
...
</React.Fragment>
)
}

TypeError: this.state.edit is not a function

I want to create update operation on ReactJS
first I set edit button as
export default class Viewcustomer extends React.Component{
constructor(props) {
super(props)
this.state = {
customers:[]
}
}
componentDidMount() { /* lifecycle method*/
axios.get(`http://localhost:5001/customers/customerView`)
.then(res => {
const customers = res.data;
this.setState({customers});
})
}
onChange = (e) => {
this.setState(
{[e.target.name]: e.target.value}
)
}
edit=personId=>{
console.log(personId);
}
render(){
return(
<div>
<br/><br/>
<div className='container' style={container}>
<h1 style={h1}>Customer Details</h1>
<div className='col-md-12' style={colmd12}>
<br/><br/>
<div className="tbl-header" style={tblheader}>
<table className="table" style ={table} >
<thead className='thead' >
<tr className='tr' >
<th >Id</th>
<th>name</th>
<th>NIC</th>
<th>type</th>
<th>Delete</th>
<th>Update</th>
</tr>
</thead>
</table>
</div>
<div className="tbl-content" style={tblcontent}>
<table className="table" style ={table} >
<tbody>
{ this.state.customers.map(person =>
<tr className='td' style={td}>
<td>{person.Id}</td>
<td>{person.name}</td>
<td>{person.NIC}</td>
<td>{person.type}</td>
<td><Link to="update"><i class="fa fa-trash-o" style={iconstyle}></i></Link></td>
<td><Link to="update"><i class="fa fa-file" style={iconstyle} onClick={()=>this.state.edit(person.Id)}></i></Link></td>
</tr>)}
</tbody>
</table>
</div>
</div>
</div>
</div>
)
}
}
the update icon routes to Updatecustomer.jsx file.
then I set the edit fuction on Update.jsx file
import React from "react";
export default class Updatecustomer extends React.Component{
constructor(props){
super(props)
this.state={
update:[]
}
}
onChange = (e) => {
this.setState(
{[e.target.name]: e.target.value}
)
}
edit=personId=>{
console.log(personId);
}
render(){
return(
<div>
</div>
)
}
}
then my browser gives the following error: (TypeError: this.state.edit is not a function)
It is very big help if you have some ideas to fix this.
The edit function is not part of your state. Use onClick={()=>this.edit(person.Id)} instead.
Just remove state part and try
this.edit(person.id)

how to implement react js pagination

i am using react-js-pagination.
i am able to fetch the data and can show the list of data. but i am trying to impelment paggination using react-js-pagination. i am able to show paggination bar in button but not able to get functionality.
here i am trying show 3 records per page.
UI
<div style={pannelFooter}>
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={3}
totalItemsCount={this.state.projectList.length}
pageRangeDisplayed={5}
onChange={this.handlePageChange}
/>
</div>
Method
handlePageChange(pageNumber) {
this.setState({ activePage: pageNumber });
}
Constructor
constructor(props) {
super(props);
this.state = {
activePage: 1,
projectList: [],
originalProjectList: []
};
this.handlePageChange = this.handlePageChange.bind(this);
}
FUll Component
import React, { Component } from 'react';
import ReactDOM from "react-dom";
import Pagination from "react-js-pagination";
import {
BrowserRouter as Router,
Route,
IndexRoute,
Link,
} from 'react-router-dom';
import ProjectDetails from './ProjectDetails';
import DashboardContainer from '../UIcomponent/DashboardContainer';
const pannelWidth = {
width: '90%'
};
const pannelHeader = {
color: 'white'
};
const pannelFooter = {
float: 'right'
};
class ProjectList extends Component {
constructor(props) {
super(props);
this.state = {
activePage: 1,
searchText: '',
isdiagram:true,
isMax:false,
projectList: [],
originalProjectList: []
};
//this.handlePageChange = this.handlePageChange.bind(this);
this.projectDetails = this.projectDetails.bind(this);
this.deleteMessage = this.deleteMessage.bind(this);
this.updateInputValue = this.updateInputValue.bind(this);
this.setSize=this.setSize.bind(this);
}
componentDidMount() {
let d = '';
$.get("http://localhost:8008/api/navigation/all", function (data) {
d = data;
this.setState({
projectList: d,
originalProjectList: d
});
}.bind(this));
}
handlePageChange(pageNumber) {
this.setState({ activePage: pageNumber });
console.log(this.state.projectList);
}
projectDetails(item, index) {
console.log(index);
}
deleteMessage(item, index) {
showconfrim("Do you want to delete this Project?", this.deleteProject(item, index));
console.log('delete');
}
deleteProject(item, index) {
$("#confirmwindow").modal('hide');
console.log('delete');
}
setSize(){
this.setState({
isMax:!this.state.isMax
});
if(!this.state.isMax){
//clear style for jquery animate;
$(this.refs.selfdiv).attr("style",null);
setTimeout(()=>{
$(this.refs.selfdiv).animate({
top:'0px',
right: '0px',
bottom: '0px',
left: '0px'
},500);
},100);
}
console.log(this.props.children);
if(this.props.children[1].props['data-event']){
var self=this;
setTimeout(()=>{
self.props.children[1].props['data-event'].call();
},700);
}
}
updateInputValue(event) {
this.setState({
searchText: event.target.value
}, function () {
let textToSearch = this.state.searchText;
let originalData = this.state.projectList;
if (textToSearch != undefined || textToSearch != '') {
let searchData = [];
for (var i = 0; i < this.state.projectList.length; i++) {
if (this.state.projectList[i].name.indexOf(textToSearch) != -1 || this.state.projectList[i].description.indexOf(textToSearch) != -1) {
searchData.push(this.state.projectList[i]);
}
}
this.setState({
projectList: searchData
});
}
if(textToSearch == '') {
this.setState({
projectList: this.state.originalProjectList,
});
}
});
}
render() {
var listItems = this.state.projectList.map((item, index) => {
return <tr key={index}>
<td onClick={e => this.projectDetails(item, index)}><a><u>{item.name}</u></a></td>
<td>{item.description}</td>
<td><i className="glyphicon glyphicon-trash" onClick={e => this.deleteMessage(item, index)}></i></td>
</tr>
});
return (
<div className="container" style={pannelWidth} ref="selfdiv">
<br />
<div className="panel panel-primary">
<div className="panel-heading">
<div className="row">
<div className="col-md-2 col-lg-2">
<h4 style={pannelHeader}>Project List</h4>
</div>
<div className="col-md-6 col-lg-6">
<input type="text" className="form-control" placeholder="Search" value={this.state.searchText} onChange={this.updateInputValue}/>
</div>
<div className="col-md-2 col-lg-2">
<button className="btn btn-sm btn-success">Create New Project</button>
</div>
<div className="col-md-2 col-lg-2">
<div className="captiontoolbar buttoncontainer">
<span onClick={this.setSize} style={pannelFooter} className={
this.state.isMax ? ("boxMaxsize glyphicon glyphicon-resize-small") : ("boxMaxsize glyphicon glyphicon-fullscreen")
}></span>
</div>
</div>
</div>
</div>
<div className="panel-body">
<table className="table table-striped">
<thead>
<tr>
<th><b>Project Name</b></th>
<th><b>Description</b></th>
<th><b>Action</b></th>
</tr>
</thead>
<tbody>
{listItems}
</tbody>
</table>
</div>
<div style={pannelFooter}>
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={3}
totalItemsCount={this.state.projectList.length}
pageRangeDisplayed={5}
onChange={this.handlePageChange.bind(this)}
/>
</div>
</div>
</div>
);
}
}
export default ProjectList;
var indexOfLastTodo = this.state.activePage * this.state.itemPerPage;
var indexOfFirstTodo = indexOfLastTodo - this.state.itemPerPage;
var renderedProjects = this.state.projectList.slice(indexOfFirstTodo, indexOfLastTodo);
var listItems = renderedProjects.map((item, index) => {
return <tr key={index}>
<td onClick={e => this.projectDetails(item, index, e)}><a><u>{item.projectName}</u></a></td>
<td>{item.description}</td>
<td><i className="glyphicon glyphicon-trash" onClick={(e) => { if (window.confirm('All its related data will be deleted. Are you sure you want to delete?')) this.deleteMessage(item, index) } } > </i></td>
<td><i className="glyphicon glyphicon-edit" id="edit" onClick={e => this.editProject(item, index, e)}></i></td>
</tr>
});
Here need to update the projectList array using slice(firstIndex, lastIndex). then subarray should be use for rander purpose.
and paggination tag should be like below
<Pagination
activePage={this.state.activePage}
itemsCountPerPage={this.state.itemPerPage}
totalItemsCount={this.state.originalProjectList.length}
pageRangeDisplayed={5}
onChange={this.handlePageChange.bind(this)}
/>
Take advantage of ES6.
So, insted of doing something like this:
this.handlePageChange = this.handlePageChange.bind(this);
You can do this:
handlePageChange = (pageNumber) => {
console.log(`active page is ${pageNumber}`);
this.setState({activePage: pageNumber});
}
Hope this helps.

why is this.state.records.amount undefined?

I am in the process of learning React and having some trouble with my state. I am trying to get a function to log this.state.records.amount to my console when the component is rendered but it shows up as undefined. If someone can figure this out it would be VERY much appreciated.
Records component:
class Records extends React.Component {
constructor(props) {
super(props);
this.state = {
records: []
}
this.handleNewRecord = this.handleNewRecord.bind(this);
this.handleDeleteRecord = this.handleDeleteRecord.bind(this);
this.surplus = this.surplus.bind(this);
this.debt = this.debt.bind(this);
this.total = this.total.bind(this);
}
componentDidMount() {
this.setState({
records: this.props.records
})
}
handleNewRecord(record) {
let records = this.state.records.slice();
records.push(record)
this.setState({
records: records
})
}
handleDeleteRecord(record) {
let records = this.state.records.slice();
let index = records.indexOf(record)
records.splice(index, 1)
this.setState({
records: records
})
}
surplus() {
console.log(this.state.records[0].amount)
}
debt() {
console.log("debt")
}
total() {
console.log("total")
}
render() {
const records = this.state.records.map((record) =>
<Record record={record} key={record.id} handleDeleteRecord={this.handleDeleteRecord}/>
)
return (
<div className="container">
<h1>Records</h1>
<div className="row">
<AmountBox panelClass="panel panel-primary" panelHeader="Surplus" calculatedAmount={this.surplus()} />
<AmountBox panelClass="panel panel-warning" panelHeader="Debt" calculatedAmount={this.debt()} />
<AmountBox panelClass="panel panel-success" panelHeader="Total" calculatedAmount={this.total()} />
</div>
<RecordForm handleNewRecord={this.handleNewRecord}/>
<table className="table">
<thead>
<tr>
<th>Date</th>
<th>Title</th>
<th>Amount</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{records}
</tbody>
</table>
</div>
)
}
}
Amount Box component:
class AmountBox extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div className="col-md-4">
<div className={this.props.panelClass}>
<div className="panel-heading">
<h3 className="panel-title">{this.props.panelHeader}</h3>
</div>
<div className="panel-body">
<p>
{this.props.calculatedAmount}
</p>
</div>
</div>
</div>
)
}
}
this.state.records[0].amount is undefined because on first render you are setting records to [] (in the constructor).
setState will trigger a second render, but in the first render the changes to state by setState will not apply.
So, you need some defensive code that makes sure that this.state.records have items.
surplus() {
this.state.records.length ? this.state.records[0].amount : 0;
}

Resources