2

I have a problem with a React component that renders a multi-part form and results from an API.

The results are updated in real time, as a user selects items via the form fields, and the form fields themselves also update to remove non-applicable options.

My API call is carried out in componentDidUpdate. I first check whether it is the form that has updated, not the results or anything else. Then I do the API call and update the state of the results, and the new field values. (There is no infinite loop! I'm quite sure of this.)

This works well in Firefox and Safari. The problem appears to be in Chrome, where the post-API call setState takes something like 4 seconds (using Chrome's performance tools, this seems to be largely the result of "componentDidUpdate Warning: Scheduled a cascading update".).

What I don't understand is:

  • It does update the state from inside componentDidUpdate, but only once (first change to selected value triggers one change of state; API results are the second state change). Surely this isn't a problem?
  • If it is a problem, why is there no problem in Firefox and Safari?
  • I have another form with basically the same code, just fewer fields and options, that does not have the same problem.

It seems that this error occurs when writing a lot of data to the component's state — it's something like 400k in this case. And, of course, only in Chrome.

I've tried looking everywhere, and all I see is stuff about infinite loops... which is not the problem here. Any suggestions or similar experiences?


Update:

  • Did some more testing on different machines/browsers. Seems the problem occurs in Chrome and Opera on both Windows and Mac; but not in Firefox on Windows or Mac, or Safari.
  • I tried running application against a test database with less data (and therefore less data loaded by API) and the problem does not occur, even in Chrome. It seems to be a very specific problem of trying to do a large setState in Chrome.

Should have included some code ... there's obviously a lot missing here; this seems to be where the problem lies.

    componentDidUpdate(prevProps, prevState) {

        if (this.state.results === prevState.results) {
            this.updateSearchParams();
            fetch(`/api/stories-in-text/search/?`+this.getSearchParams())
            .then(response => response.json())
            .then(json => {

                this.setState(
                    {
                        results: json.result,
                        theme_options: json.themes,
                        titles_options: json.titles,
                        textual_collections_options: json.textual_collections,
                        characters_in_story_body_options: json.characters_in_story_body,
                        characters_in_frame_options: json.characters_in_frame,
                        places_in_story_body_options: json.places_in_story_body,
                        places_in_frame_options: json.places_in_frame,
                        component_has_data: true,   
                    }
                );


            });
        }

    }
user2672537
  • 343
  • 1
  • 4
  • 11

1 Answers1

1

According to link - Gaearon, the warning means what it says: you scheduled a cascading update. "Cascading" means an update inside an update. React first reconciles the tree by calling render, then commits DOM changes, then calls lifecycle hooks. If you call setState in componentDidMount, you are causing React to repeat the cycle: reconcile the tree, commit DOM changes, and call the hooks. This is wasteful.

You can also get some more information here

sylvia
  • 73
  • 5
  • Thanks for link... I understand the issue here (I think). Where else can I have this API call, if not in componentDidUpdate? Also — wasteful though it is — there is no problem in Firefox, or with less data in Chrome. It's a difference of ~100ms in Firefox, ~4 seconds in Chrome! – user2672537 Nov 12 '19 at 16:51
  • You can try to solve the issue by comparing current props to the previous props, like so: ```componentDidUpdate(prevProps, prevState) { if (prevProps.data !== this.props.data) { ... } }``` You can some more information [Link1](https://www.nikpro.com.au/the-react-component-lifecycle-in-update-stage-shouldcomponentupdate-and-componentdidupdate-part-2/) and [Link2](https://stackoverflow.com/a/40745283/9250776) – sylvia Nov 12 '19 at 17:03
  • I already do this. There are exactly two updates — one where the user selects something on the form, triggering a state update; and then the one triggered in componentDidUpdate the invokes the API call and changes state based on result. – user2672537 Nov 12 '19 at 17:26
  • Have you tried doing it like this? ```componentDidUpdate(prevProps, prevState){ if (this.props.results !== prevProps.results) { ....} }``` Compare the props – sylvia Nov 13 '19 at 06:08
  • No, but the component doesn't have any props. – user2672537 Nov 13 '19 at 11:26