How to STOP React from re-rendering components on value change?

Viewed 49

I am trying to create dynamic fields based on selected options from drop-down list. Basically, UI looks something like this:

enter image description here

If I select one option UI will render in following manner:

enter image description here

Same goes for multiple options:

enter image description here

Problem is that, every time I try to change value of my dynamically generated fields, React RE-RENDERS the components and value for all fields resets. In other words, even though state is sort of changing, UI will not show anything.

Here is the code I use for rendering:

render(){
        let {testDataSpec} = this.state;
        const renderConstraints = (constraints, index) => {
            if(constraints.length === 1){
                return <Input 
                    label_val={constraints[0][0]}
                    className={this.hasError("field") ? "is-invalid" : ""}
                    type={'text'}
                    name={constraints[0]+'Value'}
                    handleChange={e => this.handleFormChange(index, 0, e)}
                    errorDiv={this.hasError("field") ? "text-danger" : "d-none"}
                    errorMsg={"Enter Constraint Value"}
                />
            }else if(constraints.length && constraints.length !== 1){
                return constraints.map((constraint, idx)=>(
                    <Input 
                        label_val={constraints[idx][0]}
                        className={this.hasError("field") ? "is-invalid" : ""}
                        type={'text'}
                        name={constraints[idx]+'Value'}
                        handleChange={e => this.handleFormChange(index, idx, e)}
                        errorDiv={this.hasError("field") ? "text-danger" : "d-none"}
                        errorMsg={"Enter Constraint Value"}
                        key={constraints[idx]+'_' + idx}
                    />
                ))
            }
        }
        return (
            <Fragment>
                <h2>Add Test Data Specification</h2>
                <hr />
                <form onSubmit={this.handleSubmit}>
                    <input type="hidden" name="id" id="id" value={testDataSpec.id} onChange={this.handleChange} />
                    <div className="row">
                        <Input 
                            label_val={"Name"}
                            className={this.hasError("name") ? "is-invalid" : ""}
                            type={'text'}
                            name={'name'}
                            handleChange={this.handleChange}
                            errorDiv={this.hasError("name") ? "text-danger" : "d-none"}
                            errorMsg={"Please enter TDS name"}
                        />
                    </div>
                    {this.state.formValues.map((element, index)=>(

                        <div key={index} className="row">
                            <div className="col-lg">
                                <Input 
                                    label_val={"Field Name"}
                                    className={this.hasError("field") ? "is-invalid" : ""}
                                    type={'text'}
                                    name={'field'}
                                    handleChange={e => this.handleFormChange(index, e)}
                                    errorDiv={this.hasError("field") ? "text-danger" : "d-none"}
                                    errorMsg={"Please enter field name"}
                                />
                            </div>
                            <div className="col-lg">
                                <Select 
                                    label_name={"DataTypes"}
                                    name={"dtype"}
                                    options={this.state.dataTypes}
                                    handleChange={e => this.handleFormChange(index,  e)}
                                />
                            </div>
                            <div className="col-lg">
                                <Select 
                                    label_name={"Constraints"}
                                    name={"constraints"}
                                    options={this.state.constraints}
                                    handleChange={e => this.handleFormChange(index, null, e)}
                                    isMultiple={true}
                                />
                            </div>
                            <div className="col-lg">
                                {
                                    renderConstraints(element.constraints, index)
                                }
                            </div>
                            <div className="col-sm">
                                {
                                    index ?
                                        <button style={{height: "50px", width: "100px", marginTop: "30px"}} type="button" className="btn btn-danger" onClick={() => this.removeFormFields(index)}>Remove</button>
                                    :null
                                }
                            </div>
                        </div>

                    ))}

                    <hr />
                    <button style={{height: "50px", width: "100px", marginRight: "20px"}} className="btn btn-success" type="button" onClick={() => this.addFormFields()}>Add</button>
                    <button style={{height: "50px", width: "100px"}} className="btn btn-primary">Save</button>
                </form>

                <div className="mt-3">
                    <pre>{JSON.stringify(this.state, null, 3)}</pre>
                </div>
            </Fragment>
        )
    }

When I have only 1x field selected, there is no issue and value is being changed accordingly, without React re-rendering components:

enter image description here

But when I have multiple fields selected, re-rendering of the components deletes the values. On the image below you can see that state is filled (Min is 2 and Max is 3), but values are not showing in the fields due to re-rendering

enter image description here

EDIT

handleFormChange and constructor:

constructor(props){
        super(props);
        this.state = {
            testDataSpec: {
                id: 0,
                name: "",
            },
            formValues: [{field: "", dtype: "", constraints: {}}],
            isLoaded: false,
            error:null,
            errors: [],
            constraints: [
                {"id": "Min",         value: "Min"},
                {"id": "Max",         value: "Max"},
                {"id": "Range",       value: "Range"},
            ],
            dataTypes: [
                {"id": "short", value: "Int16"},
                {"id": "integer", value: "Int32"},
                {"id": "long", value: "Int64"},
                {"id": "float", value: "Float32"},
                {"id": "double", value: "Float64"},
                {"id": "string", value: "String"},
            ]
        }

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

    hasError(key){
        return this.state.errors.indexOf(key) !== -1;
    }

    addFormFields(){
        this.setState(({
            formValues: [...this.state.formValues, {field: "", constraints: []}]
        }))
    }

    removeFormFields(i){
        let formValues = this.state.formValues;
        formValues.splice(i, 1);
        this.setState({formValues});
    }

    handleFormChange(i, c, e) {
        let formValues = this.state.formValues;
        if (e.target.name === "constraints"){
            //let map = new Map(Array.from(e.target.selectedOptions, option => [option.value, null]));
            //let newIterable = map.entries(map);
            //formValues[i][e.target.name] = Array.from(newIterable);
            formValues[i][e.target.name] = Array.from(e.target.selectedOptions, option => [option.value, null]);
        }else if(e.target.name.includes("Value")){
            console.log("(i, c) =  (", i, c, ")")
            let obj = [
                formValues[i]["constraints"][c][0],
                e.target.value
            ]
            formValues[i]["constraints"][c] = obj;
        }else{
            formValues[i][e.target.name] = e.target.value;
        }
        this.setState((prevState) => ({
            ...prevState,
            ["formValues"]: formValues,
        }))
        
    }
0 Answers
Related