0

I've got a SPA that has two parts, one of which allows you to choose a geographic area, and another which allows you to pick and choose countries. The country picker subscribes to the geographic area updates so that it can only put up countries that overlap your chosen geographic area. The problem happens when the render method on the CountryPanel attempts to remove countries from the selection if they are no longer in the selected geographic area. So if you select some countries in the eastern hemisphere in the CountryPanel, then go to the GeographicPanel and select an area in the western hemisphere, and the CountryPanel attempts to remove the selected countries from the store, I get the following error in the console:

Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.

The problem occurs in a call to this.props.dispatch(removeCountry(c)) in the following code:

connect((store) => {
    return {
        min_lat_val: store.session.min_latitude_val,
        min_lat_ns: store.session.min_latitude_ns,
        max_lat_val: store.session.max_latitude_val,
        max_lat_ns: store.session.max_latitude_ns,
        min_long_val: store.session.min_longitude_val,
        min_long_ew: store.session.min_longitude_ew,
        max_long_val: store.session.max_longitude_val,
        max_long_ew: store.session.max_longitude_ew,
        sel_countries: store.session.selected_countries,
        countries: store.extents.countries,
    }
})
export class CountryPanel extends React.Component {
    constructor(props) {
        super(props)
    }

    render() {
        let country_keys = Object.keys(this.props.countries).sort()
        if (country_keys.length < 1) {
            return  <div class="panel-body">
                <h3>Countries</h3>
                <p className="strong">Country data has not loaded yet!</p>
            </div>
        }
        let countries = []
        if (this.props.min_lat_val && this.props.max_lat_val &&
            this.props.min_long_val && this.props.max_long_val) {
            let tuple = []
            country_keys.forEach((c, i) => {
                const country = this.props.countries[c]
                if (extentsOverlap(
                    this.props.min_lat_val,
                    this.props.min_lat_ns,
                    this.props.max_lat_val,
                    this.props.max_lat_ns,
                    this.props.min_long_val,
                    this.props.min_long_ew,
                    this.props.max_long_val,
                    this.props.max_long_ew,
                    country)) {
                        tuple.push(<CountryCheck key={c} country={country} />)
                    if (tuple.length > 2) {
                        countries.push(tuple)
                        tuple = []
                    }
                } else {
                    if (this.props.sel_countries.has(c)) {
                        this.props.dispatch(removeCountry(c))
                    }
                }
            })
            if (tuple.length > 0) {
                countries.push(tuple)
            }
        } else {
            let tuple = []
            country_keys.forEach((c, i) => {
                tuple.push(<CountryCheck key={c} country={this.props.countries[c]} />)
                if (tuple.length > 2) {
                    countries.push(tuple)
                    tuple = []
                }
            })
            if (tuple.length > 0) {
                countries.push(tuple)
            }
        }
        const trs = countries.map((c, i) => <tr key={i}>{c}</tr>)
        return  <div class="panel-body">
            <h3>Countries</h3>
            <p>If you leave all the checkboxes unchecked, it will return waypoints in all countries that meet all the other criteria. If you select US or Canada, you will be able to select states or provinces from a further list below, or leave those checkboxes empty to select the whole country.</p>
            <p>The two letter country codes you see below are from <a href="https://en.wikipedia.org/wiki/List_of_FIPS_country_codes">FIPS 10.4</a>, not the more common <a href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">ISO 3166</a>, which is why you might not recognize some of these codes.</p>
            <p><strong>Note: Non-USA data is not as current as USA data, so carefully check this data.</strong> Actually, you should always check all this data against current official data sources.</p>
            <table class="table table-striped table-bordered">
                <tbody>
                {trs}
                </tbody>
            </table>
        </div>
    }
}

If I try what's referred to as a "workaround and proof of concept" in https://stackoverflow.com/a/43438560/3333 and change the offending line to setTimeout(() => this.props.dispatch(removeCountry(c)), 0) the warning goes away. Is that the best solution?

Community
  • 1
  • 1
Paul Tomblin
  • 179,021
  • 58
  • 319
  • 408
  • 2
    You are doing wrong. You should never setState inside render method. – Ved Apr 17 '17 at 19:20
  • @Ved so where's a better place? In the onChange callbacks on the geographic fields? – Paul Tomblin Apr 17 '17 at 19:21
  • Yup. You can. Set should be updated outside render method. – Ved Apr 17 '17 at 19:22
  • 1
    The code in your render is taking state of your app (countries and location) and its transforming it into a new state. This is the literal description of a reducer. – Sulthan Apr 17 '17 at 19:26
  • usually this is doing via `componentWillReceiveProps` [https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops](https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops) – Roman Maksimov Apr 17 '17 at 21:06
  • @Sulthan that's a very good point. Unfortunately I have the extents of the countries in a different store because it comes from a different source, but I think I can work with this. – Paul Tomblin Apr 17 '17 at 21:12
  • Perhaps your app is getting [beyond combineReducers](http://redux.js.org/docs/recipes/reducers/BeyondCombineReducers.html) and you should be looking for new ways to create your reducer so that each slice of the state can react when other parts change. – Michael Peyper Apr 17 '17 at 23:42

0 Answers0