1

In App.js, I am passing setURL(page){ ... } as a prop to HealthForm. In HealthForm, I have an input field that takes a String of an URL and a button that initiates a fetch call to my backend server and some data is received back in a promise object. I also call that.props.changeUrl(that.state.someURL);inside the promiseStatus function because that's the only place I could place it without getting the following warning:

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

However, every time that that.props.changeUrl(that.state.someURL) is called, the page re-renders. Basically -- the input field and the additional functions that were rendered due to the fetch call -- all reset. The url state in App.js gets updated though.

Why does the whole page re-renders when I'm calling the parent props?

The app does not re-render if the line that.props.changeUrl(that.state.someURL) is simply deleted but of-course it doesn't change the App state

I need the page to not re-render because vital information is rendered after the fetch call which cannot be seen since the re-render resets that route.

App.js

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            url: '',

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

    setURL(link) {
        this.setState({
            url: link
        });
    }

    render(){
        return(

            <MuiThemeProvider>
                <Router>
                    <div className="App">
                        <Route path="/" component={Header}></Route>

                        <Route path="/health" component={()=>(
                            <HealthForm changeUrl={this.setURL}/>)}></Route>

                        <Route path="/path1" component={wForm}></Route>
                        <Route path="/path2" component={xForm}></Route>
                        <Route path="/path3" component={yForm}></Route>
                        <Route path="/path4" component={zForm}></Route>

                    </div>
                </Router>
            </MuiThemeProvider>
        );
    }   
}

HealthForm.js

class HealthForm extends React.Component {
  constructor(props) {
   super(props);
    this.state = {
        exampleURL: '',
        exampleURLError: '',
        status: '',
        showStatus: false
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
 }

 validate = () => {
 //…checks for input errors
       }

 handleChange(event) {
    this.setState({
        [event.target.name]: event.target.value
    });
 }

 handleSubmit(event) {
    event.preventDefault();
    const err = this.validate();
    let that = this;
    if (!err) {
                   this.setState({
        exampleURLError: ''
        });
        console.log(this.state);
        var data = this.state.exampleURL

        fetch('htpp://...', {
                    method: 'POST',
                    body: JSON.stringify(data)
                })
                .then((result) => {
                    var promiseStatus = result.text();
                    promiseStatus.then(function (value) {
                        that.setState({
                            status: value,
                            showStatus: true
                        });
                        that.props.changeUrl(that.state.jarvisURL);
                    });
                }).catch((error) => {
                    console.log(error);
                });
    }
 }

 render() {
        return (
            <form>  
            <TextField
              ...
            />
            <br/>

             <Button variant="contained" size="small" color="primary"     onClick={e => this.handleSubmit(e)} >
                Check
            </Button>
            <br />  <br /> 

             ...

            </form>  
        );
 }
}
export default HealthForm;
Dendin
  • 163
  • 1
  • 6
  • 19
  • You commonly get this error when forgetting to remove a listener when unmounting a component, thus calling `this.setState(...)` in a listener function of a component that is no longer mounted. – ArneHugo Jul 31 '18 at 21:55

1 Answers1

1

This is happening because you're calling setState() on the App component, causing it to re-render, including re-creating all the routes you've set up. I'm not sure which router you're using exactly but it seems that it is recreating the components under the routes, probably by calling the component function that's passed in as a prop again and getting a new instance of your HealthForm component.

I assume the state you're storing inside App is required by all components in the application and that's why you're putting it there? If not, move it down into the HealthForm component, but if so maybe it's time to think about storing state externally to your components, e.g. in a state container like Redux or something else in a Flux style.

EDIT: I think the root of your problem is here:

<Route path="/health" component={()=>(<HealthForm changeUrl={this.setURL}/>)}></Route>

In the fact that a function is passed as the component prop, resulting in a new instance of the component each time. I can see why you needed to do that, to get the reference to setURL() passed into the HealthForm - it's also something that could be avoided by extracting the state out of the component.

Matt Holland
  • 2,190
  • 3
  • 22
  • 26
  • Great answer, Matt. By default react will run render() on the component when state changes; that's a basic rule of components lifecycle that people often seem to miss. This other question would also help: Does render get called any time “setState” is called? https://stackoverflow.com/questions/24718709/reactjs-does-render-get-called-any-time-setstate-is-called – leosteffen Feb 22 '19 at 19:34