การอัพเดตวัตถุด้วย setState ใน React


265

เป็นไปได้หรือไม่ที่จะอัพเดทคุณสมบัติของวัตถุด้วยsetState?

สิ่งที่ต้องการ:

this.state = {
   jasper: { name: 'jasper', age: 28 },
}

ฉันเหนื่อย:

this.setState({jasper.name: 'someOtherName'});

และนี่:

this.setState({jasper: {name: 'someothername'}})

ผลลัพธ์แรกในข้อผิดพลาดทางไวยากรณ์และข้อที่สองไม่ทำอะไรเลย ความคิดใด ๆ


5
รหัสที่สองจะได้ทำงาน แต่คุณจะได้หายไปภายในสถานที่ให้บริการage jasper
giorgim

คำตอบ:


577

มีหลายวิธีการทำเช่นนี้จะมีตั้งแต่การปรับปรุงรัฐคือการดำเนินงาน asyncเพื่อที่จะปรับปรุงวัตถุรัฐเราต้องใช้อัพเดทฟังก์ชั่นsetStateที่มี

1- หนึ่งที่ง่ายที่สุด:

ก่อนอื่นให้สร้างสำเนาจากjasperนั้นทำการเปลี่ยนแปลงที่:

this.setState(prevState => {
  let jasper = Object.assign({}, prevState.jasper);  // creating copy of state variable jasper
  jasper.name = 'someothername';                     // update the name property, assign a new value                 
  return { jasper };                                 // return new object jasper object
})

แทนที่จะใช้Object.assignเราสามารถเขียนดังนี้:

let jasper = { ...prevState.jasper };

2- การใช้โอเปอเรเตอร์การแพร่กระจาย :

this.setState(prevState => ({
    jasper: {                   // object that we want to update
        ...prevState.jasper,    // keep all other key-value pairs
        name: 'something'       // update the value of specific key
    }
}))

หมายเหตุ: Object.assignและSpread Operatorสร้างสำเนาตื้นเท่านั้นดังนั้นหากคุณกำหนดวัตถุที่ซ้อนกันหรืออาร์เรย์ของวัตถุคุณต้องใช้วิธีการอื่น


การอัพเดตอ็อบเจ็กต์สถานะซ้อนกัน:

สมมติว่าคุณได้กำหนดสถานะเป็น:

this.state = {
  food: {
    sandwich: {
      capsicum: true,
      crackers: true,
      mayonnaise: true
    },
    pizza: {
      jalapeno: true,
      extraCheese: false
    }
  }
}

ในการอัปเดตชีสชีสของวัตถุพิเศษให้ทำดังนี้

this.setState(prevState => ({
  food: {
    ...prevState.food,           // copy all other key-value pairs of food object
    pizza: {                     // specific object of food object
      ...prevState.food.pizza,   // copy all pizza key-value pairs
      extraCheese: true          // update value of specific key
    }
  }
}))

การอัปเดตอาร์เรย์ของวัตถุ:

สมมติว่าคุณมีแอพที่ต้องทำและคุณกำลังจัดการข้อมูลในแบบฟอร์มนี้:

this.state = {
  todoItems: [
    {
      name: 'Learn React Basics',
      status: 'pending'
    }, {
      name: 'Check Codebase',
      status: 'pending'
    }
  ]
}

หากต้องการอัพเดตสถานะของวัตถุใด ๆ ที่ต้องทำให้เรียกใช้แผนที่บนอาร์เรย์และตรวจสอบค่าที่ไม่ซ้ำกันของแต่ละวัตถุในกรณีที่condition=trueส่งคืนวัตถุใหม่ด้วยค่าที่อัปเดตหรือวัตถุอื่นที่เหมือนกัน

let key = 2;
this.setState(prevState => ({

  todoItems: prevState.todoItems.map(
    el => el.key === key? { ...el, status: 'done' }: el
  )

}))

คำแนะนำ:หากวัตถุไม่มีค่าที่ไม่ซ้ำกันให้ใช้ดัชนีอาร์เรย์


วิธีที่ฉันพยายามตอนนี้ทำงานแม้ว่า ... วิธีที่สอง: S
JohnSnow

7
@JohnSnow ในตัวคุณที่สองมันจะลบคุณสมบัติอื่น ๆ ออกจากjasperวัตถุทำconsole.log(jasper)คุณจะเห็นเพียงหนึ่งคีย์nameอายุจะไม่อยู่ที่นั่น :)
Mayank Shukla

1
@JohnSnow ใช่วิธีนี้เป็นที่เหมาะสมถ้าjasperเป็นobjectไม่ได้ว่าดีที่สุดหรือไม่อาจจะเป็นโซลูชั่นที่ดีอื่น ๆ ที่เป็นไปได้ :)
Mayank Shukla

6
ดีที่จะพูดถึงที่นี่ว่าไม่Object.assignกระจายคุณสมบัติการคัดลอกของตัวดำเนินการ ด้วยวิธีนี้คุณควรใช้วิธีการแก้ปัญหาเช่น lodash deepCopyฯลฯ
อเล็กซ์ Vasilev

1
การแข่งขันเป็นlet { jasper } = this.stateอย่างไร
Brian Le

37

นี่คือวิธีที่เร็วที่สุดและอ่านง่ายที่สุด:

this.setState({...this.state.jasper, name: 'someothername'});

แม้ว่าจะthis.state.jasperมีคุณสมบัติชื่อแล้ว แต่ชื่อใหม่นั้นname: 'someothername'จะถูกใช้


38
วิธีการแก้ปัญหานี้มีข้อเสียอย่างหนึ่ง: เพื่อเพิ่มประสิทธิภาพการปรับปรุงสถานะตอบสนองอาจจัดกลุ่มการปรับปรุงหลาย เป็นผลให้this.state.jasperไม่จำเป็นต้องมีสถานะล่าสุด prevStateดีกว่าใช้สัญกรณ์ด้วย
ทำบาป

33

ใช้สเปรดโอเปอเรเตอร์และ ES6 บางตัวที่นี่

this.setState({
    jasper: {
          ...this.state.jasper,
          name: 'something'
    }
})

อัปเดตนี้แจสเปอร์ แต่คุณสมบัติอื่น ๆ จะถูกลบออกจากรัฐ
iaq

11

ฉันใช้วิธีนี้

หากคุณมีสถานะซ้อนอยู่เช่นนี้

this.state = {
  formInputs:{
    friendName:{
      value:'',
      isValid:false,
      errorMsg:''
    },
    friendEmail:{
      value:'',
      isValid:false,
      errorMsg:''
    }
  }
}

คุณสามารถประกาศฟังก์ชัน handleChange ที่คัดลอกสถานะปัจจุบันและกำหนดใหม่ด้วยค่าที่เปลี่ยนแปลง

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

ที่นี่ HTML กับฟังเหตุการณ์ ตรวจสอบให้แน่ใจว่าใช้ชื่อเดียวกันกับที่ใช้กับวัตถุสถานะ (ในกรณีนี้คือ 'friendName')

<input type="text" onChange={this.handleChange} " name="friendName" />

สิ่งนี้ใช้ได้กับฉันยกเว้นฉันต้องใช้สิ่งนี้แทน:statusCopy.formInputs[inputName] = inputValue;
MikeyE

9

ฉันรู้ว่ามีคำตอบมากมายที่นี่ แต่ฉันไม่แปลกใจเลยที่พวกเขาจะสร้างสำเนาของวัตถุใหม่นอก setState แล้วก็ setState ({newObject}) สะอาดกระชับและเชื่อถือได้ ดังนั้นในกรณีนี้:

const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))

หรือสำหรับคุณสมบัติแบบไดนามิก (มีประโยชน์มากสำหรับแบบฟอร์ม)

const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))


7

ลองนี้มันน่าจะใช้ได้ดี

this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));

4
คุณกำลังกลายพันธุ์วัตถุสถานะโดยตรง ในการใช้วิธีการนี้ให้เพิ่มวัตถุใหม่เป็นแหล่งที่มา:Object.assign({}, this.state.jasper, {name:'someOtherName'})
Albizia

3

กรณีแรกย่อมเป็นข้อผิดพลาดทางไวยากรณ์

เนื่องจากฉันไม่เห็นส่วนประกอบที่เหลือของคุณจึงยากที่จะดูว่าทำไมคุณจึงซ้อนวัตถุในสถานะของคุณที่นี่ ไม่ใช่ความคิดที่ดีที่จะซ้อนวัตถุในสถานะส่วนประกอบ ลองตั้งค่าสถานะเริ่มต้นของคุณเป็น:

this.state = {
  name: 'jasper',
  age: 28
}

ด้วยวิธีนี้หากคุณต้องการอัปเดตชื่อคุณสามารถโทรได้ที่:

this.setState({
  name: 'Sean'
});

สิ่งนั้นจะบรรลุเป้าหมายที่คุณตั้งไว้หรือไม่

สำหรับแหล่งข้อมูลขนาดใหญ่ที่ซับซ้อนกว่านี้ฉันจะใช้ Redux แต่นั่นเป็นขั้นสูงมากขึ้น

กฎทั่วไปพร้อมสถานะส่วนประกอบคือใช้เพื่อจัดการสถานะ UI ของส่วนประกอบเท่านั้น (เช่นใช้งาน, ตัวจับเวลา, ฯลฯ )

ตรวจสอบข้อมูลอ้างอิงเหล่านี้:


1
ฉันใช้สิ่งนั้นเป็นตัวอย่างเท่านั้นวัตถุต้องซ้อนกัน .. ฉันน่าจะใช้ Redux แต่ฉันพยายามเข้าใจ React fundementals ..
JohnSnow

2
ใช่ฉันจะอยู่ห่างจาก Redux ในตอนนี้ ในขณะที่คุณกำลังเรียนรู้พื้นฐานพยายามทำให้ข้อมูลของคุณง่าย นั่นจะช่วยคุณหลีกเลี่ยงกรณีแปลก ๆ ที่ทำให้คุณเดินทาง 👍
mccambridge

2

ตัวเลือกอื่น: กำหนดตัวแปรของคุณออกจากวัตถุแจสเปอร์แล้วเรียกตัวแปร

ตัวดำเนินการสเปรด: ES6

this.state = {  jasper: { name: 'jasper', age: 28 } } 

let foo = "something that needs to be saved into state" 

this.setState(prevState => ({
    jasper: {
        ...jasper.entity,
        foo
    }
})

2

วิธีที่ง่ายและมีชีวิตชีวา

สิ่งนี้จะทำงาน แต่คุณต้องตั้งรหัสทั้งหมดให้กับผู้ปกครองดังนั้นผู้ปกครองจะชี้ไปที่ชื่อของวัตถุเป็น id = "jasper" และตั้งชื่อชื่อขององค์ประกอบอินพุต = คุณสมบัติภายในของวัตถุแจสเปอร์ .

handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });

ฉันเห็นด้วยกับบรรทัดแรกเนื่องจากเป็นสิ่งเดียวที่ทำงานกับวัตถุของฉันในสถานะ! นอกจากนี้ยังล้างข้อมูลที่ฉันติดอยู่ในการอัปเดตข้อมูล ไกลโดยวิธีที่ง่ายที่สุดและแบบไดนามิก ... ขอบคุณ !!
สมิ

2

คุณสามารถลองกับสิ่งนี้ : ( หมายเหตุ : ชื่อของแท็กอินพุต === ฟิลด์ของวัตถุ)

<input name="myField" type="text" 
      value={this.state.myObject.myField} 
     onChange={this.handleChangeInpForm}>
</input>

-----------------------------------------------------------
handleChangeInpForm = (e) => {
   let newObject = this.state.myObject;
   newObject[e.target.name] = e.target.value;
   this.setState({
     myObject: newObject 
   })
}

ยินดีต้อนรับสู่ Stack Overflow ในขณะที่รหัสนี้อาจตอบคำถาม แต่มีบริบทเพิ่มเติมเกี่ยวกับสาเหตุและ / หรือวิธีการที่รหัสนี้ตอบคำถามช่วยปรับปรุงมูลค่าระยะยาว วิธีตอบ
Elletlar

2

นี่เป็นอีกวิธีหนึ่งในการใช้ยูทิลิตี้immer immutabe ซึ่งเหมาะอย่างยิ่งสำหรับวัตถุที่ซ้อนกันอย่างง่ายดายและคุณไม่ควรสนใจเกี่ยวกับการกลายพันธุ์

this.setState(
    produce(draft => {
       draft.jasper.name = 'someothername
    })
)

1

นอกจากนี้ให้ทำตามวิธีของ Alberto Piras หากคุณไม่ต้องการคัดลอกวัตถุ "state" ทั้งหมด:

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let jasperCopy = Object.assign({}, this.state.jasper);
    jasperCopy[inputName].name = inputValue;

    this.setState({jasper: jasperCopy});
  }

1

คุณสามารถลองกับสิ่งนี้:

this.setState(prevState => {
   prevState = JSON.parse(JSON.stringify(this.state.jasper));
   prevState.name = 'someOtherName';
   return {jasper: prevState}
})

หรือสำหรับทรัพย์สินอื่น ๆ :

this.setState(prevState => {
   prevState = JSON.parse(JSON.stringify(this.state.jasper));
   prevState.age = 'someOtherAge';
   return {jasper: prevState}
})

หรือคุณสามารถใช้ฟังก์ชัน handleChage:

handleChage(event) {
   const {name, value} = event.target;
    this.setState(prevState => {
       prevState = JSON.parse(JSON.stringify(this.state.jasper));
       prevState[name] = value;
       return {jasper: prevState}
    })
}

และรหัส HTML:

<input 
   type={"text"} 
   name={"name"} 
   value={this.state.jasper.name} 
   onChange={this.handleChange}
/>
<br/>
<input 
   type={"text"} 
   name={"age"} 
   value={this.state.jasper.age} 
   onChange={this.handleChange}
/>

สิ่งนี้ใช้ได้โดยไม่ต้อง JSON.stringify .. this.setState (prevState => {prevState = this.state.document; prevState.ValidDate = event.target.value; prevState.ValidDateFormat = dayFormat; return {document: prevState}}); เอกสาร ..Where เป็นชนิดสถานะของวัตถุ ..
Oleg Borodko

1

โดยไม่ใช้ Async และ Await ใช้สิ่งนี้ ...

funCall(){    
     this.setState({...this.state.jasper, name: 'someothername'});
}

หากคุณใช้กับ Async และ Await ใช้สิ่งนี้ ...

async funCall(){
      await this.setState({...this.state.jasper, name: 'someothername'});
}

0

การตั้งค่านี้ใช้งานได้สำหรับฉัน:

let newState = this.state.jasper;
newState.name = 'someOtherName';

this.setState({newState: newState});

console.log(this.state.jasper.name); //someOtherName
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.