Input value not Updating after setState change… Increment and Decrement

让人想犯罪 __ 提交于 2021-02-11 15:14:35

问题


I have these increment and decrement buttons that will add or subtract. I will import the default values from props and then want to allow the user to make adjustments. So when I update the state (series) for the appropriate group it appears to update just fine... but the input value remains '14' in the HTML although I have it set to value={this.state[mppt_number].series}. Shouldn't it automatically update when the state changes?

It probably has to do with the [mppt_number] variable.... if mppt_number is =1 when creating the element, wouldn't value be value={this.state[1].series} and value={this.state[2].series} for the second time it's created? I don't know... I'm a little new to this.

import React from 'react';
//import { defaultValues } from './Calculator';


//Will be passed in as props just have it here for now to ease testing. This can range from 1 to N
const defaultValues = {
    1:{
        array_isc: 10.88,
        array_power: 3834.5999999999995,
        array_vmp: 497,
        array_voc: 584.3312579999999,
        series: 14,
        string: 1,
    },


2: {array_isc: 9.00,
    array_power: 3834.5999999999995,
    array_vmp: 600,
    array_voc: 584.3312579999999,
    series: 12,
    string: 1},
}

const mppt = 2


class Tool extends React.Component{
    constructor(props){
        super(props)

        this.state = {
            stringSizingElement : null, 
        }
        this.handleStringChange =this.handleStringChange.bind(this)
        this.stringSizingElementHelper = this.stringSizingElementHelper.bind(this)

    }

    componentDidMount() {
        let s = {};
        for (let i = 1; i <= mppt; i++) {
          s[`${i}`] = defaultValues[i];
        }
    
        this.setState((prevState) => ({ ...s }), () => {
            console.log(this.state);
            this.createStringSizingElementDefaults()
        });
      }


      //Create this element mppt_number of times for each MPPT
    stringSizingElementHelper( mppt_number){
        let stringSizing = (   
            <div className='container' id ={mppt_number} key = {mppt_number}>
                <div className="row justify-content-center">
                <label>MPPT {mppt_number}</label>
                    </div>
                <div className="row justify-content-center" id= {`${mppt_number}`}>
                    <div className="col-4">
                    <label className='container'>Series</label>
                        <div className={`input-group container ${`${mppt_number}`}`} >
                            
                            <button className ='btn btn-primary decrement' onClick={this.handleSeriesChange} >-</button>
                            <input className="form-control series" min="0" name="quantity" value={this.state[mppt_number].series} type="number" disabled/>
                            <button className ='btn btn-primary increment' onClick={this.handleSeriesChange} >+</button>
                        </div>
                    </div>
            </div>
            <br/>
            <br/>
        </div>    
        );

        return stringSizing;
    }


    createStringSizingElementDefaults(){
        let temp = []
        //Creating element for each mppt (1:{} , 2: {}) of times for each MPPT This number ranges. 
        for (let i=1 ; i <= mppt ; i++) {
          const  result = this.stringSizingElementHelper(i)
            temp.push(result)
    
        }

        this.setState({stringSizingElement: temp})
    }
    
 
    handleSeriesChange(e){
        if(e.target.classList.contains('decrement')){
            //Get ID to Figure out which MPPT Is being changed
            const mppt_number = e.target.parentNode.parentNode.parentNode.parentNode.id
            var newValues = {...this.state[mppt_number]}
            newValues.series = this.state[mppt_number].series - 1
            this.setState({[mppt_number] : newValues}, ()=>{
                console.log(this.state[mppt_number].series)
            });

        }else if(e.target.classList.contains('increment')){
            const mppt_number = 
           e.target.parentNode.parentNode.parentNode.parentNode.id
            var newValues = {...this.state[mppt_number]}
            newValues.series = this.state[mppt_number].series + 1
            console.log(newValues.series)
            this.setState({[mppt_number] : newValues}, ()=>{
                console.log(this.state[mppt_number].series)
            })

        }else{
            console.log('Error??')
        }
    }


    render(){

        return(
            <div className="temp container">
                <div className="form-row  justify-content-center">
                    <label htmlFor="sel2"><h4>String Sizing Tool:</h4> 
                    </label>
                </div>
                {this.state.stringSizingElement}
                <br/>
                <br/>
                {undefined}
                <br/>
                <br/>
            </div>
        )
    }
}

Help this girl out? :D

UPDATE:

Didn't store these array of JSX in a state anymore. I create them between render and return now. Now it's not "rendering" when it's stored in a state? I still get the same results. :(

Now I the function returns the array instead of setting a state.

createStringSizingElementDefaults(){
        let temp = []
        //Creating element for each mppt (1:{} , 2: {}) of times for each MPPT This number ranges. 
        for (let i=1 ; i <= mppt ; i++) {
          const  result = this.stringSizingElementHelper(i)
            temp.push(result)
    
        }

        return temp

    }

This array is now created in between render and return.

render(){
 let stringSizingElement = this.createStringSizingElementDefaults()

 

    return(
        <div className="temp container">
            <div className="form-row  justify-content-center">
                <label htmlFor="sel2"><h4>String Sizing Tool:</h4></label>
            </div>
            {stringSizingElement}
            <br/>
            <br/>
            {undefined}
            <br/>
            <br/>
        </div>
    )
}

SOLUTION CODE AFTER HELP:

import React from 'react';
//import { defaultValues } from './Calculator';
//import './xxxxx.css';

//Will be passed in as props just have it here for now to ease testing
const defaultValues = {
   
     1: {array_isc: 10.88,
        array_power: 3834.5999999999995,
        array_vmp: 497,
        array_voc: 584.3312579999999,
        series: 14,
        string: 1,
    },

    2: {array_isc: 9.00,
        array_power: 3834.5999999999995,
        array_vmp: 600,
        array_voc: 584.3312579999999,
        series: 12,
        string: 1},

    3: {array_isc: 1.00,
            array_power: 3834.5999999999995,
            array_vmp: 600,
            array_voc: 584.3312579999999,
            series: 12,
            string: 1},
}




class Tool extends React.Component{
    constructor(props){
        super(props)

        this.state = {
            
        }
        this.handleStringChange =this.handleStringChange.bind(this)
        this.handleSeriesChange =this.handleSeriesChange.bind(this)
      

    }

    componentDidMount() { 
        let s = {};
        Object.keys(defaultValues).map((mppt_number, i) => (
            s[`${mppt_number}`] = defaultValues[mppt_number]
        ))
    
        this.setState((prevState) => ({ ...s }), () => {
            console.log(this.state);
           
        });
      }





    handleSeriesChange(e){
        if(e.target.classList.contains('decrement')){
            //Get ID to Figure out which MPPT Is being changed

            console.log(e.target.parentNode.parentNode.parentNode.parentNode.id)
            const mppt_number = e.target.parentNode.parentNode.parentNode.parentNode.id
            var newValues = {...this.state[mppt_number]}
            newValues.series = this.state[mppt_number].series - 1
            this.setState({[mppt_number] : newValues}, ()=>{
                console.log(this.state[mppt_number].series)
            })
            


        }else if(e.target.classList.contains('increment')){
            console.log(e.target.parentNode.parentNode.parentNode.parentNode.id)
            const mppt_number = e.target.parentNode.parentNode.parentNode.parentNode.id
            var newValues = {...this.state[mppt_number]}
            newValues.series = this.state[mppt_number].series + 1
            this.setState({[mppt_number] : newValues}, ()=>{
                console.log(this.state[mppt_number].series)
            })

        }else{
            console.log('Error?? Maybe remove this')
        }

    }


    render(){

     

        return(
            <div className="temp container">
                <div className="form-row  justify-content-center">
                    <label htmlFor="sel2"><h4>String Sizing Tool:</h4></label>
                </div>
                
                {Object.keys(this.state).map((mppt_number, i) => (
                    <div className='container' id ={mppt_number} key = {mppt_number}>
                            <div className="row justify-content-center">
                            <label>MPPT {mppt_number}</label>
                                </div>
                            <div className="row justify-content-center" id= {`${mppt_number}`}>
                                <div className="col-4">
                                <label className='container'>Series</label>
                                    <div className={`input-group container ${`${mppt_number}`}`} >
                                        
                                        <button className ='btn btn-primary decrement' onClick={this.handleSeriesChange} >-</button>
                                        <input className="form-control series" min="0" name="quantity" value={this.state[mppt_number].series} type="number" disabled/>
                                        <button className ='btn btn-primary increment' onClick={this.handleSeriesChange} >+</button>
                                    </div>
                                </div> 
                            </div>
                        <br/>
                        <br/>
                    </div> 
))}
                <br/>
                <br/>
                {undefined}
                <br/>
                <br/>
            </div>
        )
    }
}

回答1:


setStste only overwrites old state in new state, so:

            this.setState({[mppt_number] : newValues}, ()=>{
                console.log(this.state[mppt_number].series)
            })

won't update [mppt_number] : newValues, but replaces state into {[mppt_number] : newValues}. If you want to:

            this.setState((oldState) => ({...oldState, ...{[mppt_number] : newValues}}), ()=>{
                console.log(this.state[mppt_number].series)
            })

the above will work. Giving an object as a first parameter of setState doesn't cause updating as you intended. Giving a function Instead of an object, working as above.

If you are new to React, and not for using job immediately, I recommend to use functional components and hooks instead of class method. It's simple and makes outlook of code so good. Try it!

Add: Problem happens here:

        this.setState({stringSizingElement: temp})

temp contains React nodes and is rendered as initial state. tringSizingElement in state is always rendered nodes, so variables are fixed as constant values.

To avoid this, stop storing nodes into state. Nodes in state are NOT scope of React DOM rendering, it means they are not updated their variables.

    createStringSizingElementDefaults(){
        let temp = []
        for (let i=1 ; i <= mppt ; i++) {
          const  result = this.stringSizingElementHelper(i)
            temp.push(result)
    
        }

        return temp
    }

To make createStringSizingElementDefaults nodes directly, this problem won't happen.

Add 2:

this is a troublesome problem. What this points must different what you think. If call this.func.bind(this), this in func is fixed. In other word, this.state is never changed. So even if calling his.createStringSizingElementDefaults() over and over again, results are same! To avoid both problems? One of answers is that call bind again. But it is not recommended in performance reason. Another is simple. Not to access state in member functions. If you want to, give it as parameters.

And to achieve that, "let view depend on state only" helps you. To decide to make view from state only, doesn't cause to confuse. What this means is following example:

class Example extends React.Component{
    constructor(props){
        super(props)

        this.state = {
            somelist: [1, 2, 3],
        }
        this.handleAppend = this.handleAppend.bind(this)
        this.handleRemove = this.handleRemove.bind(this)
    }
    handleAppend(){
        this.setState({someList} => {
            let s = [...someList]
            s.push(spmeList[someList.length - 1])
            return {someList: s}
        })
    }
    handleRemove(){
        this.setState({someList} => {
            let s = [...someList]
            s.pop()
            return {someList: s}
        })
    }
    render(){
        return(
            <div>
                <button onClick={this.handleAppend}>Append</button>
                <button onClick={this.handleRemove}>Remove</button>
                {this.state.someList.map(v => <div>{v}</div>)}
            </div>
        )
    }
}

You can confirm that view is decided only state. To change state requires to push button and fire setState via onClick event. Data flows one-way. This is IMPORTANT.

Summarize,

  • setState takes update function
  • Do not include nodes in state
  • Do not access this.state in member funstions
    • If you want to, give it as parameters
  • Keep data flow one-way

If you have a special circumstance, I don't stop to use class-style components, but not, I recommend functional component style strongly.



来源:https://stackoverflow.com/questions/65365921/input-value-not-updating-after-setstate-change-increment-and-decrement

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!