I have a react component that is in charge of performing a network request using fetch. The API response will be shown on cards within the component.I have defined the structure of the response in the state as movimientos. But when updating the state inside filtrarDatos function with the response, an infinite loop is created and fetch requests are performed infinitely.
Here is my code:
export class Datepicker extends Component {
constructor(props) {
super(props)
this.state = {
startDate: "",
endDate: "",
focusedInput: "",
movimientos: {}
}
}
filtrarDatos(startDateString, endDateString) {
if (startDateString !== '' && endDateString !== '') {
const empresa = {
FECHA_INICIAL: startDateString,
FECHA_FINAL: endDateString
};
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(empresa)
}
fetch('http://localhost:4000/api/movimiento/filtrarfecha', options)
.then((res) => res.json())
.then((data) => {
const { movimientos } = data
console.log({ movimientos })
this.setState({ movimientos })
})
.catch((err) => console.log(err))
}
}
render() {
const endDateString = this.state.endDate && this.state.endDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
const startDateString = this.state.startDate && this.state.startDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
return (
<div className="DatePicker">
<DateRangePicker
startDate={this.state.startDate} // momentPropTypes.momentObj or null,
endDate={this.state.endDate} // momentPropTypes.momentObj or null,
onDatesChange={({ startDate, endDate }) => this.setState({ startDate, endDate })} // PropTypes.func.isRequired,
focusedInput={this.state.focusedInput} // PropTypes.oneOf([START_DATE, END_DATE]) or null,
onFocusChange={focusedInput => this.setState({ focusedInput })} // PropTypes.func.isRequired,
endDatePlaceholderText={"Fecha inicial"}
startDatePlaceholderText={"Fecha final"}
displayFormat={"DD/MM/YYYY"}
numberOfMonths={1}
isOutsideRange={() => false}
showClearDates={true}
/>
{this.filtrarDatos(startDateString, endDateString)}
</div>
)
}
}
To be more clear the error is in the following part of the code, if I comment on the status update the program works correctly and only makes a single request. I am new to react and I cannot understand what is happening.
.then((data) => {
const { movimientos } = data
console.log({ movimientos })
this.setState({ movimientos })
})
This is a screenshot of my console during infinite network requests
Its happening because this.filtrarDatos is being called after each re-render (state changes), creating a infinite loop (change data, render, change data, rend...)
You can move the { this.filtrarDatos(startDateString, endDateString) } to componentDidMount:
Remove { this.filtrarDatos(startDateString, endDateString) }
Add this lifecycle function, after constructor:
componentDidMount() {
const endDateString = this.state.endDate && this.state.endDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
const startDateString = this.state.startDate && this.state.startDate.format('YYYY-MM-DD') + "T13:47:14.985+00:00";
this.filtrarDatos(startDateString, endDateString);
}
You are calling {this.filtrarDatos(startDateString, endDateString)} in your render body, then in this method you update state, so it creates an infinite loop because react rerender your component after state changes.
Related
I am getting the data from api. I am displaying Feature ID, DisplayOrder textbox in the rows. User can change the Display Order value in the multiple rows. How to update the information using Post API? I am passing one value FeatureID and DisplayOrder in form submit. Please help to pass all the values that are changed(FeatureID, DisplayOrder) in form submit. If suppose FeatureID 11 and FeatureID 13 Display order changes, then form submit needs to pass these information only.
{"FeatureID":"11","DescriptionText":"Travel","FeatureText":Feature2,"DisplayOrder":"1","Date":"08/30/2011","FeatureName":"Research"},
{"FeatureID":"12","DescriptionText":"Sport","FeatureText":Feature3,"DisplayOrder":"2","Date":"08/30/2011","FeatureName":"Research"},
{"FeatureID":"13","DescriptionText":"Art","FeatureText":Feature4,"DisplayOrder":"3","Date":"08/30/2011","FeatureName":"Research"}]
import React from "react";
export class EditFeatures extends React.Component {
constructor(props) {
super(props);
this.state = {
FeatureID: "",
DisplayOrder: "",
DescriptionText: "",
FeatureText: "",
Feature: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.DisplayFeatures();
}
DisplayFeatures() {
fetch(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => response.json())
.then((data) => {
this.setState({
Feature: data,
loading: false,
});
});
}
handleSubmit(event) {
event.preventDefault();
const FeatureID = this.state.FeatureID;
const DisplayOrder = this.state.DisplayOrder;
const data = {
FeatureID,
DisplayOrder,
};
fetch(REQUEST_URL, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.catch((error) => console.error("Error:", error))
.then((response) => console.log("Success", data));
window.location.href = "/";
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<table>
<tbody>
{this.state.Feature.map((item, index) => {
return [
<tr key={item.FeatureID}>
<td>
<input
type="text"
id={item.FeatureID}
name="DisplayOrder"
value={item.DisplayOrder}
onChange={(ev) => {
const newFeature = this.state.Feature.map((f) => {
if (f.FeatureID == ev.target.id) {
f.DisplayOrder = ev.target.value;
}
return f;
});
this.setState({ Feature: newFeature });
}}
/>
</td>
<td>{item.DescriptionText}</td>
<td>{item.FeatureTex}</td>
</tr>,
];
})}
</tbody>
</table>
<button type="submit" name="submit">
Update
</button>
</form>
</div>
);
}
}
export default Edit_Features;
The answer is simple, just sort Feature array on DisplayOrder in handleSubmit like this:
import React from "react";
export class EditFeatures extends React.Component {
constructor(props) {
super(props);
this.state = {
FeatureID: "",
DisplayOrder: "",
DescriptionText: "",
FeatureText: "",
Feature: [],
};
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.DisplayFeatures();
}
DisplayFeatures() {
fetch(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then((response) => response.json()) // you passed Content-Type: "application/xml" as request header but here you use response.json, remove Content-Type header if server API returns json
.then((data) => {
this.setState({
Feature: data.map((feature) => ({ ...feature, changed: false })),
loading: false,
});
});
}
handleSubmit(event) {
event.preventDefault();
const FeatureID = this.state.FeatureID;
const DisplayOrder = this.state.DisplayOrder;
const Feature = this.state.Feature;
const data = {
FeatureID,
DisplayOrder,
Feature, // this is how you pass an array to server, how will the server deserialize this depends on the framework used there
};
const self = this;
fetch(REQUEST_URL, {
method: "POST",
body: JSON.stringify(data),
headers: { "Content-Type": "application/json" },
})
.then((response) => response.json())
.catch((error) => console.error("Error:", error))
.then((response) => {
/**
* sort manipulates the array so we clone the Feature array before sorting it
* we pass comparator function to sort so that we sort on DisplayOrder
*/
const newFeature = [...this.state.Feature];
newFeature.sort((f1, f2) => f2.DisplayOrder - f1.DisplayOrder);
self.setState({ Feature: newFeature });
});
window.location.href = "/"; // ok why does this exist?!!
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<table>
<tbody>
{this.state.Feature.map((item) => {
return [
<tr key={item.FeatureID}>
<td>
<input
type="text"
id={item.FeatureID}
name="DisplayOrder"
value={item.DisplayOrder}
onChange={(ev) => {
// this is the proper way to update an element inside an array
const newFeature = [...this.state.Feature];
// I prefer === over == to avoid errors
const featureIndex = newFeature.findIndex(
(f) => f.FeatureID === ev.target.id
);
newFeature[featureIndex].DisplayOrder =
ev.target.value;
this.setState({ Feature: newFeature });
}}
/>
</td>
<td>{item.DescriptionText}</td>
<td>{item.FeatureTex}</td>
</tr>,
];
})}
</tbody>
</table>
<button type="submit" name="submit">
Update
</button>
</form>
</div>
);
}
}
export default EditFeatures;
this way when you click button submit, if the POST request to the server succeeds, the table will be updated according to DisplayOrder.
Note
If the request to the server fails for any reason the table won't be updated, if you don't care about the response of the server just sort the Feature array outside the .then before issuing the request.
I have get my data from firebase , loop through them and display them to dom.
then I added a delete button and send a delete request using axios and it's delete from firebase but the dom doesn't rerender. I set a deleting state to change it in 'then' block but even when I change the state it dosn't rerender!
what can I do?
class Orders extends Component {
state = {
orders: [],
loading: true,
deleting: false,
};
componentDidMount() {
axios
.get('/order.json')
.then((res) => {
// console.log(res.data);
const fetchedOrders = [];
for (let key in res.data) {
fetchedOrders.push({ ...res.data[key], id: key });
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch((err) => {
this.setState({ loading: false });
});
}
deleteHandler = (id) => {
axios.delete(`/order/${id}.json`).then((res) => {
this.setState({ deleting: true });
console.log(res, this.state.deleting);
});
};
render() {
return (
<div>
{this.state.orders.map((order) => (
<Order
key={order.id}
ingredients={order.ingredient}
price={order.price}
id={order.id}
delete={() => this.deleteHandler(order.id)}
/>
))}
</div>
);
}
}
You have to update the orders state while calling deleteHandler! Try this code!
import React from 'react';
import axios from 'axios';
// YOUR OTHER IMPORT GOES HERE
class Orders extends Component {
constructor(props) {
this.state = {
orders: [],
loading: true,
deleting: false,
}
}
componentDidMount() {
axios
.get('/order.json')
.then((res) => {
// console.log(res.data);
const fetchedOrders = [];
for (let key in res.data) {
fetchedOrders.push({ ...res.data[key], id: key });
}
this.setState({ loading: false, orders: fetchedOrders });
})
.catch((err) => {
this.setState({ loading: false });
});
}
deleteHandler = (id) => {
this.setState({
orders: this.state.orders.filter(orderValue => orderValue.id !== id)
})
axios.delete(`/order/${id}.json`).then((res) => {
this.setState({ deleting: true });
console.log(res, this.state.deleting);
});
};
render() {
return (
<div>
{this.state.orders.map((order) => (
<Order
key={order.id}
ingredients={order.ingredient}
price={order.price}
id={order.id}
delete={() => this.deleteHandler(order.id)}
/>
))}
</div>
);
}
}
I am using React-autosuggest in my code but the issue i am facing is this that whenever i am clikcing any suggestion i am getting error that
Uncaught TypeError: Cannot read property 'trim' of undefined
Here is my code
var subjectsToBeSearched= []
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0
? []
:subjectsToBeSearched.filter(
lang => lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
};
const getSuggestionValue = suggestion => suggestion.name;
`
const renderSuggestion = suggestion => <div>{suggestion.name}</div>;
export default class Searchbar extends Component {
state = {
language_id: "",
subjects: [],
value: "",
suggestions: []
};
onChange = event => {
this.setState({
value: event.target.value
},()=>console.log(this.state.value));
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
componentWillMount() {
let languageid = localStorage.getItem("language_id");
var userdata = window.localStorage.getItem("userdata");
if (languageid == null) {
localStorage.setItem("language_id", 0);
}
this.setState({ language_id: languageid, userdata: JSON.parse(userdata) });
this.getAllSubjects();
}
getAllSubjects = async () => {
let details = {
language_id: this.state.language_id
};
this.setState({
response: fetch("http://18.221.47.207:3000/get_subjects", {
method: "GET",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cache-Control": "max-age=31536000"
}
})
.then(response => response.json())
.then(responseJson => {
this.setState(
{
subjects: responseJson
},
() => {
let subjectsToBeFind = this.state.subjects.map(item => {
return { id: item.subject_id, name: item.subject_name };
});
subjectsToBeSearched=subjectsToBeFind
}
);
})
.catch(error => {
this.setState({
loading: false
});
swal("Warning!", "Check your network!", "warning");
console.log(error);
})
});
};
render() {
const { value, suggestions } = this.state;
const inputProps = {
value,
onChange: this.onChange
};
return (
<div className={`${styles.InputHeaderSearchDiv} `}>
<Autosuggest
inputProps={inputProps}
className={`${styles.InputHeaderSearch}`}
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
// alwaysRenderSuggestions={true}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
/>
<div className={`${styles.SearchIcon}`}>
<img src={SearchIcon} alt="search" />
</div>
</div>
);
}
}
So when i click on any suggestion i just need to console and do some stuff but right away it throws error
When you click a suggestion, the value of the "value" key in the inputProps is undefined thats the reason that you are getting a cannot trim error.
A workaround I did is add a props called "onSuggestionSelected" ( Documentation for onSuggestionSelected found here ) and added a function that set the value of the "value" key in the inputProps to whatever value you want your input tag should have after the click event.
THIS IS HOW MY AUTOSUGGEST LOOKSLIKE
HERE IS MY FUNCTION
in my case simply setting value of inputProps to string did it
<Autosuggest
inputProps: {
value: whateverValue.toString()
I have two components i.e. Project component and MainContainer. Maincontainer will fetch data again and again using fetchData method, so in this I am able to change the url as well as component when I move forward but during backward it will not change the component but url can change.
Project Component:
conditionalRendering(project) {
let text;
if (project.attributes.folder_id) {
text = <Link to={`/folder/${project.attributes.folder_id}`}>
{project.attributes.name}</Link>
} else {
text = <span>{project.attributes.name}</span>
}
return text;
}
MainContainer Component:
componentDidMount() {
const paths = this.props.location.pathname.split('/')
const id = paths[paths.length - 1]
console.log('componentDidMount')
axios.get(this.state.url, {
params: {
folder_id: id
}
})
.then(response => {
console.log('Axios response of componentdid mount')
console.log(response)
this.setState({
isLoading: !this.state.isLoading,
documents: response.data.data
})
})
}
fetchData(folder_id) {
this.setState({
isLoading: true,
})
console.log('updating state fetch data')
axios.get(this.state.url, {
params: {
folder_id: folder_id
}
})
.then(response => {
console.log('Axios response inside fetchData')
console.log(response)
this.setState({
isLoading: false,
documents: response.data.data,
})
})
}
selectRow(document) {
this.setState({
selectedRow: true,
rowDetails: document
})
}
componentDidUpdate(prevProps, prevState){
console.log('componentDidUpdate')
if (prevState.isLoading === false) {
console.log('if will run isLoading is false')
this.setState({ isLoading: true })
console.log('if will run isLoading is true' )
const paths = this.props.location.pathname.split('/')
const id = paths[paths.length - 1]
axios.get(this.state.url, {
params: {
folder_id: id
}
})
.then(response => {
console.log('Axios response componentdidupdate')
console.log(response)
this.setState({
isLoading: !this.state.isLoading,
documents: response.data.data
})
})
}
}
documentsCollection() {
let documentComponents;
if (this.state.documents.length > 0 ) {
documentComponents = this.state.documents.map(document => {
return (
<tr key={document.id} onClick={() => this.selectRow(document)}>
<span className="file-thumbnail float-right"><i className={document.attributes.is_folder ? 'fas fa-folder' : 'far fa-file'}></i></span>
<td data-label="Name">
<Link to={`/folder/${document.attributes.id}`} onClick={() => this.fetchData(document.attributes.id)}>{document.attributes.filename}</Link>
</tr>
)
})
return documentComponents;
}
}
Router Component:
<Switch>
<Route exact path="/" component= {Project} />
<Route path="/folder/:id" component= {MainContainer} />
</Switch>
If you are already in Maincontainer and only the id value has changed, componentDidMount will not be activated. Please use componentDidUpdate and compare props.
I am having a problem here with my code. When the Data is undefined or equals false it just has the error Maximum depth exceeded. But how does this happen? The setState method should only be called once and if the method sets the state to false, id shouldnt set any states anymore, or not? Where is my error.
class App extends Component {
constructor(props) {
super(props);
this.changeLogin = this.changeLogin.bind(this);
//Loggedin is false
this.state = {
loggedIn: undefined
};
}
componentDidMount() {
communicate("getToken").then(data => {
//if no data is available
if (!data) {
if (this.state.loggedIn === undefined)
this.setState({
loggedIn: false
});
}
//Fetch the key
else {
let reqBody =
"refresh_token=" + data.refresh_token + "&grant_type=refresh_token";
//Fetch via refresh_token
fetch(conf.apiDomain + "/oauth/token", {
method: "POST",
body: reqBody,
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF8",
Authorization: "Basic Y2xpZW50OlRlc3Q="
}
})
.then(response => response.json())
.then(json => {
//if error exists
if (json.error) {
throw new Error("Token not valid");
} else {
//save the new tokens in the json file
communicate("saveToken", json).then(res => {
//Set the loginstate
this.setState({
loggedIn: true
});
});
}
})
.catch(err => {
if (this.state.loggedIn === undefined) {
this.setState({
loggedIn: false
});
}
});
}
});
}
//only for the logincomponent
changeLogin() {
this.setState({
loggedIn: !this.state.loggedIn
});
}
render() {
return (
<Router>
<MuiThemeProvider theme={theme}>
<Decider loggedIn={this.state.loggedIn} />
<Route
path="/login/"
render={() => {
return <Login changeLogin={this.changeLogin} />;
}}
/>
<AppRoute
path="/app/"
component={Pyl}
loggedIn={this.state.loggedIn}
/>
</MuiThemeProvider>
</Router>
);
}
}
Error appears at: if(!data) if there is no data, if the data is there but invalid, its at the .catch statement
Full Error MSG: 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.
StackTrace: https://ibb.co/jZKKJT1
Github for full code: https://github.com/precodeeu/pyl
jsfiddle(works): https://jsfiddle.net/L8anmhvx/3/
It seems the real error is coming from Decider and your routing.
In Decider when loggedIn = false, it appears you are redirecting back, thus creating the infinite loop.