Infinite Looping Errorr in ReactJS - reactjs

Layout.js
import React, {Component} from "react"
import Aircraft from "./Aircraft"
class Layout extends Component {
constructor(props) {
super(props)
this.state = {
test: true,
acn: ""
}
}
WhatIsAircraftName = (acn) => {
this.setState({
acn: acn
})
}
render() {
return (
<div>
<div className="mainD posRel hidO">
<div className="posRel hidO topD">
</div>
<div className="posRel hidO bottomD container">
<Aircraft clk={this.WhatIsAircraftName} />
</div>
</div>
</div>
)
}
}
export default Layout
Aircraft.js
import React, {Component} from "react"
import Loading from "./Loading"
class Aircraft extends Component {
constructor(props) {
super(props)
this.state = {
aircraft: [],
loading: false,
utilized: 0
}
}
componentDidMount() {
let mod_this = this
this.setState({
loading: true
})
fetch("https://some-endpoint")
.then(function(response) {
if (response.ok) {
return response.json()
}
})
.then(function(myJson) {
mod_this.setState({
aircraft: myJson,
loading: false
})
})
}
DisplayAircraft() {
let ac = this.state.aircraft
this.props.clk(ac.data[0].ident)
return (
<div className="acD posRel hidO selected">
{
<h2>{ac.data[0].ident}</h2>
}
</div>
)
}
render() {
const {aircraft} = this.state
return (
<div className="posRel hidO leftD">
<h1>Aircrafts</h1>
{
!aircraft || aircraft.length <= 0 || this.state.loading ?
<Loading /> :
this.DisplayAircraft()
}
</div>
)
}
}
export default Aircraft
When I run my app, I get setState loop error:
Unhandled Rejection (Invariant Violation): 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 reason I am doing it like this is the Aircraft component will get the Aircraft ID which I want to send to another child component, hence I am sending it to the parent to use it as props for another component.
Section is:
Layout.WhatIsAircraftName [as clk]

The problem is that your render method is not pure and is updating state.
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser. - React Docs
In your parent component, you have WhatIsAircraftName which is calling setState. Whenever this method is invoked, it will trigger a state update which will trigger a render. In the parent's render you are passing the Aircraft component the prop clk which is assigned to WhatIsAircraftName. Then, in Aircraft's render, it's calling DisplayAircraft, which is invoking the prop clk which starts us back at the top.
Layout#render -> Aircraft#render -> DisplayAircraft -> this.props.clk -> WhatIsAircraftName -> this.setState -> Layout#render -> etc. (infinite loop).
This loop needs to be broken, setState should never be invoked within render.

Try something like this, I've moved the logic around that you are no longer updating the state from the render function.
import React, {Component} from "react"
import Loading from "./Loading"
class Aircraft extends Component {
constructor(props) {
super(props)
this.state = {
aircraft: [],
loading: false,
utilized: 0
}
}
componentDidMount() {
let mod_this = this
this.setState({
loading: true
})
fetch("https://some-endpoint")
.then(function(response) {
if (response.ok) {
return response.json()
}
})
.then(function(myJson) {
this.props.clk(myJson.data[0].ident)
mod_this.setState({
aircraft: myJson,
loading: false
})
})
}
render() {
const {aircraft} = this.state
return (
<div className="posRel hidO leftD">
<h1>Aircrafts</h1>
{
!aircraft || aircraft.length <= 0 || this.state.loading ?
<Loading /> :
(<div className="acD posRel hidO selected">
<h2>{aircraft.data[0].ident}</h2>
</div>)
}
</div>
)
}
}
export default Aircraft
This should at least get it working for you but based on what I can see in your code I do have another suggestion. It would be much simpler to do the API call from the Layout component and then pass the aircraft information down to the Aircraft component via Props. In your current code you are having to pass the data back up via a function you passed via a prop which makes it all a little more complicated. React tends to be much easier to work with when you are passing data down the chain.
Hope that helps!

Related

ReactJS : infinite loop rendering component

I have an issue on ReactJS about an infinite loop of an Axios request link to a componentDidMount function.
Here the thing :
First : user receive a link by email (with a token in parameter)
Second : When they clicked on the link, they arrived on a webpage where they can reset their password. I need to check if the token is always available : depending of that, the render will be different.
To check the token, I made a POST request via a componentDidMount.
Finally, I get the right render BUT my request is call again and again, creating an infinite loop on my server. It seems that my child component is re-construct again and again.
Here's my code :
Child component :
import React from 'react';
import {Container} from 'reactstrap';
export default class ResetPassword extends React.Component {
constructor(props) {
super(props);
console.log('CONSTRUCT')
}
componentDidMount() {
if (this.props.appState.loading ≡ false) {
console.log('componentDidMount')
let url = window.location.pathname;
let verifToken = url.substr(15);
this.props.checkToken(verifToken); //Make axios call on App.js
}
}
render() {
const {expiredToken} = this.props.appState;
console.log(expiredToken)
return (
<div className="ResetPassword">
<Container>
{expiredToken === true ?
(<div>VOTRE TOKEN A EXPIRE</div>
) : (<div>CHANGER MON MOT DE PASSE</div>)}
</Container>
</div>
)
}
}
Parent Component :
import axios from 'axios';
import ResetPassword from "./components/SignUp/ResetPwd";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
expiredToken : null
};
this.checkToken = this.checkToken.bind(this);
}
checkToken(token) {
console.log('checkToken');
this.setState({loading: true}, () => {
let url = `${this.state.config.apiURL}/checkToken`;
let method = 'post';
axios.request({
url: url,
method: method,
data: {
token: token
}
}).then(results => {
if (results.data === null) {
this.setState({
loading: false,
expiredToken: true
})
} else {
console.log(results.data);
this.setState({
loading: false,
expiredToken: false
});
}
})
})
}
render() {
const ResetPwd = (props) => {
return (
<div>
<ResetPassword appState={this.state} {...props} checkToken={this.checkToken}/>
</div>
)
};
}
}
And in my console DevTool, I have my 5 console.log() which turn into an infinite loop :
CONSTRUCT --> console.log() from constructor in child
expiredToken --> console.log() from render in child
ComponentDidMount → console.log() from componentDidMount
verifToken → console.log() from componentDidMount
checkToken --> console.log() from parent
Remove checkToken from ResetPassword component.
Instead of calling checkToken from ResetPassword,called it within Parent component and pass the data using state to ResetPassword component..
<ResetPassword appState={...this.state} {...props}/>
i think after you load the childComponent for fist time and the function checkToken is called, the state loading is setted to false. you are also forcing a rerender from the parentcomponent with setSatate and with it you are forcing also the mounting from the Chilcomponent and the componentDidMount method from the child. after the first render if you try to print the loading state im sure it would be always false, after first true.
try to create local states for each child or think again a new implementation of the function.

React Redux: Delay between component inner content while updating

I have a component that have 2 components inside of it:
MyComp {
render (
html of My Comp..
<Loading show={this.props.isLoading}/>
<ErrorMessage show={this.props.hasError}/>
)
}
When it is receiving data, it shows the Loading.
When the loading is complete, it receive something like:
{
isLoading: false,
hasError: true
}
But in screen, the loading close like 2s before the hasError displays.
Both components are built in the same strategie:
class Loading extends Component {
constructor(props) {
super(props);
this.state = {isLoading : props.show};
}
componentWillReceiveProps(nextProps) {
this.setState({ isLoading: nextProps.show });
}
render() {
if (this.state.isLoading) {
return (
<div className="loading">
<div className="loading-message">
Carregando...
</div>
</div>);
}
return ('');
}
}
export default Loading;
Not exactly an answer for this issue, as i can't be sure where the delay can come from.
But according to your code i would suggest to not use a local state and try to sync it with external props.
This can lead to bugs (maybe related to your issue?) as componentWillReceiveProps can get invoked even when no new props received, beside it is in deprecation process since react V16.2.
Instead i would just read directly from this.props:
class Loading extends Component {
render() {
if (this.props.isLoading) {
return (
<div className="loading">
<div className="loading-message">
Carregando...
</div>
</div>);
}
return ('');
}
}
Again, not sure it is directly related to your issue but it is a better practice.

What is best approach to set data to component from API in React JS

We have product detail page which contains multiple component in single page.
Product Component looks like:
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
Here we have 3 APIs for
- Details
- Similar Product
- Other Products
Now from Detail API we need to set data to these components
<Gallery/>
<Video/>
<Details/>
<Contact/>
In which component we need to make a call to API and how to set data to other components. Lets say we need to assign a,b,c,d value to each component
componentWillMount(props) {
fetch('/deatail.json').then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error('Something went wrong ...');
}
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
OR
Do we need to create separate api for each components?
Since it's three different components you need to make the call in the component where all the components meet. And pass down the state from the parent component to child components. If your app is dynamic then you should use "Redux" or "MobX" for state management. I personally advise you to use Redux
class ParentComponent extends React.PureComponent {
constructor (props) {
super(props);
this.state = {
gallery: '',
similarPdts: '',
otherPdts: ''
}
}
componentWillMount () {
//make api call and set data
}
render () {
//render your all components
}
}
The Product component is the best place to place your API call because it's the common ancestor for all the components that need that data.
I'd recommend that you move the actual call out of the component, and into a common place with all API calls.
Anyways, something like this is what you're looking for:
import React from "react";
import { render } from "react-dom";
import {
SearchBar,
Gallery,
Video,
Details,
Contact,
SimilarProd,
OtherProd
} from "./components/components";
class Product extends React.Component {
constructor(props) {
super(props);
// Set default values for state
this.state = {
data: {
a: 1,
b: 2,
c: 3,
d: 4
},
error: null,
isLoading: true
};
}
componentWillMount() {
this.loadData();
}
loadData() {
fetch('/detail.json')
.then(response => {
// if (response.ok) {
// return response.json();
// } else {
// throw new Error('Something went wrong ...');
// }
return Promise.resolve({
a: 5,
b: 6,
c: 7,
d: 8
});
})
.then(data => this.setState({ data, isLoading: false }))
.catch(error => this.setState({ error, isLoading: false }));
}
render() {
if (this.state.error) return <h1>Error</h1>;
if (this.state.isLoading) return <h1>Loading</h1>;
const data = this.state.data;
return (
<div>
<SearchBar/>
<Gallery a={data.a} b={data.b} c={data.c} d={data.d} />
<Video a={data.a} b={data.b} c={data.c} d={data.d} />
<Details a={data.a} b={data.b} c={data.c} d={data.d} />
<Contact a={data.a} b={data.b} c={data.c} d={data.d} />
<SimilarProd/>
<OtherProd/>
</div>
);
}
}
render(<Product />, document.getElementById("root"));
Working example here:
https://codesandbox.io/s/ymj07k6jrv
You API calls will be in the product component. Catering your need to best practices, I want to make sure that you are using an implementation of FLUX architecture for data flow. If not do visit phrontend
You should send you API calls in componentWillMount() having your state a loading indicator that will render a loader till the data is not fetched.
Each of your Components should be watching the state for their respective data. Let say you have a state like {loading:true, galleryData:{}, details:{}, simProducts:{}, otherProducts:{}}. In render the similar products component should render if it finds the respective data in state. What you have to do is to just update the state whenever you receive the data.
Here is the working code snippet:
ProductComponent:
import React from 'react';
import SampleStore from '/storepath/SampleStore';
export default class ParentComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
loading:true,
}
}
componentWillMount () {
//Bind Store or network callback function
this.handleResponse = this.handleResponse
//API call here.
}
handleResponse(response){
// check Response Validity and update state
// if you have multiple APIs so you can have a API request identifier that will tell you which data to expect.
if(response.err){
//retry or show error message
}else{
this.state.loading = false;
//set data here in state either for similar products or other products and just call setState(this.state)
this.state.similarProducts = response.data.simProds;
this.setState(this.state);
}
}
render () {
return(
<div>
{this.state.loading} ? <LoaderComponent/> :
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details/>
<Contact/>
{this.state.similarProducts && <SimilarProd data={this.state.similarProducts}/>}
{this.state.otherProducts && <OtherProd data={this.state.otherProducts}/>}
</div>
</div>
);
}
}
Just keep on setting the data in the state as soon as you are receiving it and render you components should be state aware.
In which component we need to make a call to API and how to set data
to other components.
The API call should be made in the Product component as explained in the other answers.Now for setting up data considering you need to make 3 API calls(Details, Similar Product, Other Products) what you can do is execute the below logic in componentDidMount() :
var apiRequest1 = fetch('/detail.json').then((response) => {
this.setState({detailData: response.json()})
return response.json();
});
var apiRequest2 = fetch('/similarProduct.json').then((response) => { //The endpoint I am just faking it
this.setState({similarProductData: response.json()})
return response.json();
});
var apiRequest3 = fetch('/otherProduct.json').then((response) => { //Same here
this.setState({otherProductData: response.json()})
return response.json();
});
Promise.all([apiRequest1,apiRequest2, apiRequest3]).then((data) => {
console.log(data) //It will be an array of response
//You can set the state here too.
});
Another shorter way will be:
const urls = ['details.json', 'similarProducts.json', 'otherProducts.json'];
// separate function to make code more clear
const grabContent = url => fetch(url).then(res => res.json())
Promise.all(urls.map(grabContent)).then((response) => {
this.setState({detailData: response[0]})
this.setState({similarProductData: response[1]})
this.setState({otherProductData: response[2]})
});
And then in your Product render() funtion you can pass the API data as
class Product extends Component {
render() {
return (
<div>
<Searchbar/>
<Gallery/>
<Video/>
<Details details={this.state.detailData}/>
<Contact/>
<SimilarProd similar={this.state.similarProductData}/>
<OtherProd other={this.state.otherProductData}/>
</div>
);
}
}
And in the respective component you can access the data as :
this.props.details //Considering in details component.

Call a function on application startup in react

I'm trying to call a function from application startup. The function reads data from JSON via dataVar (set elsewhere) and tries to load it into {items} for further consumption:
const dataVar = JSONStuff;
class Global extends Component {
constructor(props) {
super(props);
this.state = {
query: '',
items: []
}
this.init();
}
// componentDidMount() {
// This doesn't work either!
// this.init();
// }
init() {
let { items } = dataVar;
this.setState({items});
}
render() {
return (
<div className="Global">
<Gallery items={this.state.items}/>
</div>
)
}
}
Then in Gallery.js:
import React, { Component } from 'react';
class Gallery extends Component {
render() {
return (
<div>
<h3>gallery:</h3>
{
this.props.items.map((item, index) => {
let {title} = item.name;
return (
<div key={index}>{title}</div>
)
})
}
</div>
)
}
}
export default Gallery;
Not sure why Global can't call a function inside of itself. I've tried with and without "this." I either get error to where the app won't complile or I get:
"Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op."
First of all, it's a warning, you probably better not call setState in componentDidMount.
My suggestion 1: assign value to state in constructor
constructor(props) {
super(props);
this.state = {
query: '',
items: dataVar.items,
};
}
Suggestion 2:
Do inside the componentWillReceiveProps
componentWillReceiveProps(nextProps) {
const { dataVar: items } = nextProps; // pass dataVar as props
this.setState({
items,
});
}
Plus try to debug your props and pay attention on your console for errors.

ReactJS: How can I change dynamically inserted Component's data

I'm trying to change children Component to another component by using state. This injects new Component correctly, however, if I want to change its props dynamically, nothing is changing. componentWillReceiveProps isn't triggered.
In my scenario, I'll have many components like TestComponent (nearly 20-30 components) and they all have different HTML layout (also they have sub components, too). I switch between those components by selecting some value from some list.
Loading all those components initially doesn't seem a good idea I think. On the other hand, I haven't found anything about injecting a Component inside main Component dynamically.
Here is a very basic example of what I want to achieve. When clicking on the button, I insert TestComponent inside App. After that, on every one second, I increment a state value which I try to bind TestComponent but, the component value is not updating.
If I use commented snippet inside setInterval function instead of uncommented, it works but I have to write 20-30 switch case for finding the right component in my real code (which I also wrote when selecting a value from list) so, I want to avoid using that. Also, I'm not sure about the performance.
So, is this the correct approach, if so, how can I solve this problem? If it is wrong, what else can I try?
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
component: <p>Initial div</p>,
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
this.setState({
component: <TestComponent currentValue={this.state.componentData} />
});
setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
})
// This will update TestComponent if used instead of above
/*this.setState({
componentData: this.state.componentData + 1,
component: <TestComponent currentValue={this.state.componentData} />
});*/
}, 1000)
}
render() {
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{this.state.component}
</div>
)
}
}
class TestComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
currentValue: this.props.currentValue
};
}
componentWillReceiveProps(nextProps) {
this.setState({
currentValue: nextProps.currentValue
});
}
render() {
return (
<p>Current value: {this.state.currentValue}</p>
)
}
}
ReactDOM.render(
<App />
,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>
To dynamically render the child components you can use React.createElement method in parent, which results in invoking different components, this can be used as, below is sample code, hope it helps.
getChildComponent = (childComponentName) => {
const childComponents = {
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
},
componentProps = Object.assign({}, this.props,this.state, {
styles: undefined
});
if (childComponents[childComponentName]) {
return React.createElement(
childComponents[childComponentName],
componentProps);
}
return null;
}
render(){
this.getChildComponents(this.state.childComponentName);
}
Here in the render function, pass the component name, and child will render dynalicaaly. Other way of doing this can be, make childComponents object as array , look below fora sample
const childComponents = [
TestComponent1,
TestComponent2,
TestComponent3,
TestComponent4
]
Note: You have to import all child components here in parent, these
are not strings.
That's because as Facebook mentions in their React documentation.
When you call setState(), React merges the object you provide into the current state.
The merging is shallow
For further information read the documentation
So for this case the only modified value will be componentData and component won't trigger any updates
Solution
A better case to solve this issue is using Higher-Order components (HOC) so the App component doesn't care which component you are trying to render instead It just receives a component as a prop so you can pass props to this component base on the App state.
Also, you don't need a state in TestComponent since you get the value as a prop and it's handled by App.
I also added a condition to prevent adding multiples setInterval
class App extends React.Component {
interval;
constructor(props) {
super(props);
this.state = {
componentData: 0
};
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler = () => {
if (!this.interval) {
this.setState({
componentData: this.state.componentData + 1
});
this.interval = setInterval(() => {
this.setState({
componentData: this.state.componentData + 1
});
}, 1000);
}
}
render() {
let Timer = this.props.timer;
return(
<div>
<h4>Click the button</h4>
<button onClick={this.onClickHandler}>Change Component</button>
{!this.state.componentData ? <p>Initial div</p> : <Timer currentValue={this.state.componentData} />}
</div>
)
}
}
class TestComponent extends React.Component {
render() {
const { currentValue } = this.props;
return (
<p>Current value: {currentValue}</p>
)
}
}
ReactDOM.render(<App timer={TestComponent} /> ,document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.1/react-dom.js"></script>
<div id="app" style="width: 200px; height: 200px;"></div>

Resources