问题
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