Annah Isenberg
Annah Isenberg

Reputation: 157

Why is this function call in render() creating an infinite loop?

I want to call a function in render() which will update the state. But when I do that, it gives me this error message:

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.

I don't understand why this is happening because I'm not directly setting the state in my render(), I'm setting it in my setInsightUrl() function.

I've tried using different lifecycle functions but couldn't get any to work. I'm not sure how else to write this function.

import React, { Component } from 'react'
import "../../css/tabs/Tabs.css"
import {connect} from "react-redux"

class InsightPage extends Component {
    constructor() {
        super();

        this.state = {
            insightUrlState: null
        }

        this.setInsightUrl = this.setInsightUrl.bind(this);
    }

    setInsightUrl(url) {
        this.setState({
            insightUrlState: url
        })

        console.log(this.state.insightUrlState, 'INSIGHTTTTTT URLLLLLbgnhjm,k.l')
    }

    render() {
        this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
            obj.children.forEach(child => {
                child.insights.forEach(insight => {
                    if (insight.insightName === this.props.insightNameReducer) {
                        {this.setInsightUrl(insight.insightURL)}
                    }
                })
            })
        })

        return (
            <div className={this.props.drawerOpen ? "tab_container2" : "tab_container" }>
                <h1>Hello from Insight</h1>
                <iframe frameBorder="0" style={{width: "100%", height: "70vh"}} src="https://insighttbdashboards.verizon.com/t/DigtalAnalytics/views/Digital_Analytics/Chat_Dashboard?iframeSizedToWindow=true&:embed=y&:showAppBanner=false&:display_count=no&:showVizHome=no#2" />
            </div>
        )
    }
}

const mapStateToProps = state => ({
    drawerOpen: state.SideDrawerReducer.open,
    sideTreeMenu: state.SideDrawerReducer.menu,
    insightNameReducer: state.SideDrawerReducer.insightName
  })

export default connect(mapStateToProps)(InsightPage);

It should update the state with the url I am passing into the function in the render block.

Upvotes: 2

Views: 1228

Answers (3)

rickdenhaan
rickdenhaan

Reputation: 11298

From the code you posted (I'm not sure if that is the full code for your component) there's no need to determine the insight url in the render() function. If you do want to determine it in the render function (which should be the last thing your component does) then you shouldn't need to put it in the state, you should just use a local variable for it.

But if you want it in the state, you can either do it in the constructor:

constructor(props) {
    super(props);

    let insightUrlState = null;
    props.sideTreeMenu.dynamicMenu.forEach(obj => {
        obj.children.forEach(child => {
            child.insights.forEach(insight => {
                if (insight.insightName === props.insightNameReducer) {
                    insightUrlState = insight.insightURL;
                }
            });
        });
    });

    this.state = { insightUrlState };
}

With an additional use of a lifecycle method if you want to update the state when the props change:

componentDidUpdate(prevProps, prevState) {
    // depending on how many items are in these arrays, you might want to
    // wrap this in a check to see if this.props.sideTreeMenu.dynamicMenu has
    // changed from prevProps.sideTreeMenu.dynamicMenu and/or if
    // this.props.insightNameReducer has changed from prevProps.insightNameReducer
    let insightUrlState = null;
    this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
        obj.children.forEach(child => {
            child.insights.forEach(insight => {
                if (insight.insightName === this.props.insightNameReducer) {
                    insightUrlState = insight.insightURL;
                }
            });
        });
    });

    if (prevState.insightUrlState !== insightUrlState) {
        this.setState({ insightUrlState });
    }
}

Or, alternatively, you can use the getDerivedStateFromProps function to determine the insightUrlState value just before rendering (using this function, you don't need to use the constructor or componentDidUpdate options):

static getDerivedStateFromProps(props) {
    let insightUrlState = null;
    props.sideTreeMenu.dynamicMenu.forEach(obj => {
        obj.children.forEach(child => {
            child.insights.forEach(insight => {
                if (insight.insightName === props.insightNameReducer) {
                    insightUrlState = insight.insightURL;
                }
            });
        });
    });

    return { insightUrlState };
}

Upvotes: 2

Victor Babel
Victor Babel

Reputation: 122

Just because you are calling setState in a function defined outside of render (setInsightUrl) doesn't mean you aren't calling it within render, render potentially calls setInsightUrl when the right conditions are met, and thus can potentially loop forever.

Perhaps you could update the state only if it actually is changing:

setInsightUrl(url) {
  if (this.state.insightUrlState != url) {
    this.setState({
      insightUrlState: url
    })

    console.log(this.state.insightUrlState, 'INSIGHTTTTTT URLLLLLbgnhjm,k.l')
  }
}

Upvotes: 2

Alejandro Garcia Anglada
Alejandro Garcia Anglada

Reputation: 2403

this.props.sideTreeMenu.dynamicMenu.forEach(obj => {
  obj.children.forEach(child => {
     child.insights.forEach(insight => {
       if (insight.insightName === this.props.insightNameReducer) {
           {this.setInsightUrl(insight.insightURL)}
       }
     })
  })
})

This block is not valid JSX, you might need to move that to componentDidMount.

You can't call setState inside render, otherwise will cause a re-render, so it will go again to render and so on... That's why you got that error.

Upvotes: -1

Related Questions