William Papsco
William Papsco

Reputation: 255

React setState changes child's props

So my goal is to have one react component that essentially lets you have a few of a child component if you press a button. I want to manage the state in the top-level component, and when you hit submit, I want the state of this component to essentially be an array of objects, one element for each component in the list. My idea for a solution was to render a couple of the child components and pass them an index. Then, I give them the upper-level onChange function for them to call with the index. The problem with this is that it works until I call this.setState in the upper-level onChange function. For some reason, calling this.setState causes my index parameter to always be the highest number index in the list of child elements.

Here is AddressesDataForm.js:

export default class AddressesDataForm extends React.Component {

constructor(props) {
    super(props);
    if (this.props.onChange) {
        functions.onChange = this.onChange;
    }
    if(this.props.onSubmit) {
        functions.onSubmit = this.props.onSubmit;
    }
    this.state = {
        'tableName': 'Addresses',
        states: [
            'Gotta',
            'Hook',
            'Into',
            'State',
            'Api',
            'Still'
        ],
        county_disabled: true,
        counties: {
            'Gotta': ['a', 'b', 'c'],
            'Hook': ['d', 'e', 'f'],
            'Into': ['e', 't', 'c'],
            'State': ['e', 't', 'c'],
            'Api': ['e', 't', 'c'],
            'Still': ['e', 't', 'c']
        },
        use_counties: [],
        countries: [
            'United States of America',
            'Elsewhere'
        ],
        index: this.props.index
    }
    console.log(this.props.index);
}

onChange = (name, value) => {
    if (name == 'state') {
        if (value.length > 0) {
            this.setState({
                county_disabled: false,
                use_counties: this.state.counties[value]
            });
        } else {
            this.setState({
                county_disabled: true
            });
        }
    }
    console.log(this.props.index); //notice this.props.index, tried this.state as well and it didn't work either
    this.props.onChange(name, value, this.props.index);
}

render() {
    return (
        <DataForm tableName={this.state.tableName} onSubmit={functions.onSubmit.bind(this)} {...this.props}>
            <SelectField
                menuItems={['Home', 'Work', 'Other']}
                defaultValue="Home"
                className='md-cell md-cell--2 md-cell--middle'
                onChange={functions.onChange.bind(this, "address_type_select")}
            />
            <TextField
                label="Street Address Line 1"
                maxLength={128}
                className='md-cell md-cell--10'
                onChange={functions.onChange.bind(this, "address_line_1")}
            />
            <TextField
                label="Street Address Line 2"
                maxLength={128}
                className='md-cell md-cell--2-desktop-offset md-cell--10'
                onChange={functions.onChange.bind(this, "address_line_2")}
            />
            <TextField
                label="City"
                maxLength={64}
                className='md-cell md-cell--2-desktop-offset md-cell--5'
                onChange={functions.onChange.bind(this, "city")}
            />
            <SelectField
                label="State"
                maxLength={64}
                className='md-cell md-cell--2'
                menuItems={this.state.states}
                onChange={this.onChange.bind(this, "state")}
            />
            <TextField
                label="ZIP code"
                className='md-cell md-cell--3'
                onChange={functions.onChange.bind(this, "zip_code")}
                required
            />
            <SelectField
                label="Country"
                className='md-cell md-cell--2-desktop-offset md-cell--10'
                menuItems={this.state.countries}
                defaultValue={this.state.countries[0]}
                onChange={functions.onChange.bind(this, "country")}
            />
            <h6 className="md-cell md-cell--2-desktop-offset md-cell--10">Location Information</h6>
            <SelectField
                label="County"
                className='md-cell md-cell--2-desktop-offset md-cell--10'
                menuItems={this.state.use_counties}
                onChange={this.onChange.bind(this, "county")}
                helpText={this.state.county_disabled ? 'Select a state' : ''}
                disabled={this.state.county_disabled}
            />
            <SelectionControl
                id={"switch-primary-address" + this.props.index}
                className='md-cell md-cell--12 md-cell--2-desktop-offset'
                type="switch"
                label="Primary address"
                onChange={functions.onChange.bind(this, "primary_address")}
                checked={this.props.isPrimary}
            />
            {this.props.noAddButton ?
                null :
                <div className="md-cell">
                    <Button
                        style={{ display: "inline-block" }}
                        floating mini secondary
                        onClick={this.props.onAddClicked}>
                        add circle
                    </Button>
                    <p style={{ display: "inline-block", margin: "10px" }}>Add address</p>
                </div>}
            {this.props.noButton ? null : <h6 className="md-cell md-cell--12">* Required Fields</h6>}
            <p>{this.props.index}</p>
        </DataForm>
    );
}

}

and AddressesDeck.js

export default class AddressesDeck extends React.Component {

constructor(props) {
    super(props);
    this.state = {
        addresses: 3,
        primaryAddress: 0,
        data: {}
    };
}

onAddClicked = () => {
    this.setState({
        addresses: this.state.addresses + 1,
        primaryAddress: this.state.addresses
    });
}

onChange = (name, value, index) => {
    console.log(index); //properly logs the index
    // this.setState(prevState => ({ //unless this is un-commented, then it always logs 2
    //     'something unrelated':value
    // }));
    console.log(this.state);
    // if (name == 'primary_address') {
    //     this.setState({
    //         primaryAddress: index
    //     });
    // }
}

render() {
    return (
        // <div>
        //     {[...new Array(this.state.addresses)].map((_, i) => (
        //         <Paper id="main" key={i}>
        //             <AddressesDataForm
        //                 onChange={this.onChange.bind(this, i)}
        //                 onSubmit={functions.onSubmit.bind(this)}
        //                 key={i}
        //                 index={i}
        //                 onAddClicked={this.onAddClicked.bind(this)}
        //                 noButton={i != this.state.addresses - 1}
        //                 noAddButton={i != this.state.addresses - 1 || this.state.addresses == 3}
        //                 noTitle={i != 0}
        //                 isPrimary={this.state.primaryAddress == i} />
        //         </Paper>
        //     ))}
        // </div>
        <div>
            <Paper id="main" key={0}>
                <AddressesDataForm
                    onChange={this.onChange}
                    onSubmit={functions.onSubmit.bind(this)}
                    key={0}
                    index={0}
                    onAddClicked={this.onAddClicked.bind(this)}
                    noButton={0 != this.state.addresses - 1}
                    noAddButton={0 != this.state.addresses - 1 || this.state.addresses == 3}
                    noTitle={0 != 0}
                    isPrimary={this.state.primaryAddress == 0} />
            </Paper>
            <Paper id="main" key={1}>
                <AddressesDataForm
                    onChange={this.onChange}
                    onSubmit={functions.onSubmit.bind(this)}
                    key={1}
                    index={1}
                    onAddClicked={this.onAddClicked.bind(this)}
                    noButton={1 != this.state.addresses - 1}
                    noAddButton={1 != this.state.addresses - 1 || this.state.addresses == 3}
                    noTitle={1 != 0}
                    isPrimary={this.state.primaryAddress == 1} />
            </Paper>
            <Paper id="main" key={2}>
                <AddressesDataForm
                    onChange={this.onChange}
                    onSubmit={functions.onSubmit.bind(this)}
                    key={2}
                    index={2}
                    onAddClicked={this.onAddClicked.bind(this)}
                    noButton={2 != this.state.addresses - 1}
                    noAddButton={2 != this.state.addresses - 1 || this.state.addresses == 3}
                    noTitle={2 != 0}
                    isPrimary={this.state.primaryAddress == 2} />
            </Paper>
        </div>
    );
}

}

Upvotes: 0

Views: 124

Answers (1)

Oblosys
Oblosys

Reputation: 15106

This probably happens because in the constructor, you assign this.onChange to a functions object, which is shared between all instances of your AddressesDataForm component. When rendering each instance the first time, the constructor was just called, and functions.onChange will have the correct value. However, when setting the parent's state and rerendering each instance, no constructor is called, and functions.onChange in each of the three renderings will refer to the third this.onChange (which calls this.props.onChange with an index of 2.)

It's a little hard to see without running the code, but I would guess that simply using this.onChange would fix the problem. Also, to prevent confusion, you could rename the prop passed down to AddressesDataForm to something other than onchange.

Upvotes: 1

Related Questions