Dendin
Dendin

Reputation: 173

React Re-renders When Parent State is Changed Through Child Component

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;

Upvotes: 1

Views: 1575

Answers (1)

Matt Holland
Matt Holland

Reputation: 2210

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.

Upvotes: 1

Related Questions