ReactJS : infinite loop rendering component - reactjs

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.

Related

How to render updated state in react Js?

I am working on React Js in class component I am declaring some states and then getting data from API and changing that state to new value but React is not rendering that new value of state. but if I console.log() that state it gives me new value on console.
My code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState({ unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}
This is printing 2 on console but rendering 0 on screen. How can I get updated state(2) to render on screen.
and if I visit another page and then return to this page then it is rendering new value of state (2).
Please call getUnread() function in componentDidMount, something like this
componentDidMount() {
this.getUnread()
}
This is because in React class components, while calling setState you it is safer to not directly pass a value to set the state (and hence, re-render the component). This is because what happens that the state is set as commanded, but when the component is rerendered, once again the state is set back to initial value and that is what gets rendered
You can read this issue and its solution as given in react docs
You pass a function that sets the value.
So, code for setState would be
this.setState((state) => { unread: data.count });
Hence, your updated code would be :
class Home extends Component {
constructor(props) {
super(props);
this.state = {
unread: 0,
}
this.getUnread()
}
getUnread = async () => {
let data = await Chatapi.get(`count/${this.props.auth.user.id}/`).then(({ data }) => data);
this.setState((state) => { unread: data.count });
console.log(this.state.unread)
}
render() {
const { auth } = this.props;
return (
<div>
{this.state.unread}
</div>
)
}

How to cache react component in client side?

Is there a way to cache react component in client side. If a user comes a page say A and then navigate to another page say B, when again he comes back to A I want render should not execute ,no api call should be executed, the page should be served from cache .
You can cache state
let cachedState = null;
class ExampleComponent extend React.Component {
constructor(props) {
super(props);
this.state = cachedState !== null ? cachedState : {
apiData: null
}
}
componentWillUnmount() {
cachedState = this.state;
}
componentDidMount() {
if (this.state.apiData === null) {
this.loadApiData()
.then(apiData => {
this.setState({apiData});
});
}
}
loadApiData = () => {
// code to load apiData
};
}
As long as the input props are not getting changed, you can use React.memo().
(This is not useMemo Hook. Please don't get confused)
const Greeting = React.memo(props => {
console.log("Greeting Comp render");
return <h1>Hi {props.name}!</h1>;
});
Read this article for further clarifications -> https://linguinecode.com/post/prevent-re-renders-react-functional-components-react-memo

Infinite Looping Errorr in 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!

Called componentDidMount twice

I have a small react app. In App.js I have layout Sidenav and Content area. The side nav is shown on some page and hid from others. When I go to some components with sidenav, sidenav flag is set by redux and render the component again, in the componentDidMount I have api call, and it is executed twice.
class App extends Component {
renderSidebar = () => {
const {showNav} = this.props;
return showNav ? (
<TwoColumns>
<Sidenav/>
</TwoColumns>) : null;
};
render() {
const {showNav} = this.props;
const Column = showNav ? TenColumns : FullColumn;
return (
<Row spacing={0}>
{this.renderSidebar()}
<Column>
<Route exact path="/measurements/:id/:token/:locale/measure"
component={MeasurementPage}/>
</Column>
</Row>
)
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(App);
I tried to use shouldComponentUpdate to prevent the second API call
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
props.toggleSidenav(false);
}
shouldComponentUpdate(nextProps) {
return !nextProps.showNav === this.props.showNav;
}
componentDidMount() {
// This is executed twice and made 2 api calls
this.props.getMeasurement(params);
}
render() {
return <h1>Some content here</h1>;
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(MeasurementPage);
Did someone struggle from this state update and how manage to solve it?
This props.toggleSidenav(false) might cause side effect to your component lifecycle. We use to do this kind of stuff inside componentWillMount and it has been depreciated/removed for a reason :). I will suggest you move it inside componentDidMount
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
// props.toggleSidenav(false); // remove this
}
shouldComponentUpdate(nextProps) {
return nextProps.showNav !== this.props.showNav;
}
componentDidMount() {
if(this.props.showNav){ //the if check might not necessary
this.props.toggleSidenav(false);
this.props.getMeasurement(params);
}
}
render() {
return <h1>Some content here</h1>;
}
}
The comparison should be
shouldComponentUpdate(nextProps) {
return !(nextProps.showNav === this.props.showNav)
}
The problem is that !nextProps.showNav negate showNav value instead of negating the role expression value, and that is why you need an isolation operator.
It's No call twice anymore.
componentDidMount() {
if (this.first) return; this.first = true;
this.props.getMeasurement(params);
}

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.

Resources