Programing

결과 : setState에서 state.item [1]을 어떻게 업데이트합니까?

lottogame 2020. 5. 5. 19:32
반응형

결과 : setState에서 state.item [1]을 어떻게 업데이트합니까? (JSFiddle 사용)


사용자가 자신의 양식을 디자인 할 수있는 앱을 만들고 있습니다. 예를 들어 필드 이름과 포함해야 할 다른 열의 세부 사항을 지정하십시오.

컴포넌트는 여기 에서 JSFiddle로 사용 가능 합니다 .

내 초기 상태는 다음과 같습니다.

var DynamicForm = React.createClass({
  getInitialState: function() {
   var items = {};
   items[1] = { name: 'field 1', populate_at: 'web_start',
                same_as: 'customer_name',
                autocomplete_from: 'customer_name', title: '' };
   items[2] = { name: 'field 2', populate_at: 'web_end',
                same_as: 'user_name', 
                    autocomplete_from: 'user_name', title: '' };

     return { items };
   },

  render: function() {
     var _this = this;
     return (
       <div>
         { Object.keys(this.state.items).map(function (key) {
           var item = _this.state.items[key];
           return (
             <div>
               <PopulateAtCheckboxes this={this}
                 checked={item.populate_at} id={key} 
                   populate_at={data.populate_at} />
            </div>
            );
        }, this)}
        <button onClick={this.newFieldEntry}>Create a new field</button>
        <button onClick={this.saveAndContinue}>Save and Continue</button>
      </div>
    );
  }

사용자가 값을 변경할 때 상태를 업데이트하고 싶지만 올바른 객체를 타겟팅하기가 어렵습니다.

var PopulateAtCheckboxes = React.createClass({
  handleChange: function (e) {
     item = this.state.items[1];
     item.name = 'newName';
     items[1] = item;
     this.setState({items: items});
  },
  render: function() {
    var populateAtCheckbox = this.props.populate_at.map(function(value) {
      return (
        <label for={value}>
          <input type="radio" name={'populate_at'+this.props.id} value={value}
            onChange={this.handleChange} checked={this.props.checked == value}
            ref="populate-at"/>
          {value}
        </label>
      );
    }, this);
    return (
      <div className="populate-at-checkboxes">
        {populateAtCheckbox}
      </div>
    );
  }
});

this.setState업데이트하려면 어떻게해야 items[1].name합니까?


update불변 도우미를 사용할 수 있습니다 .

this.setState({
  items: update(this.state.items, {1: {name: {$set: 'updated field name'}}})
})

또는을 shouldComponentUpdate()사용하여 수명주기 방법 에서이 항목의 변경 사항을 감지하지 못하는 경우 ===상태를 직접 편집하고 구성 요소를 다시 렌더링하도록 할 수 있습니다. 이는 @limelights의 답변과 동일합니다. 객체를 상태에서 벗어나 편집합니다.

this.state.items[1].name = 'updated field name'
this.forceUpdate()

편집 후 추가 :

콜백 함수를 상태 유지 상위에서 상태 변경을 트리거해야하는 하위 컴포넌트로 전달하는 방법에 대한 예제는 반응 훈련 에서 단순 컴포넌트 통신 학습을 확인하십시오 .


잘못된 방법!

handleChange = (e) => {
    const { items } = this.state;
    items[1].name = e.target.value;

    // update state
    this.setState({
        items,
    });
};

의견에서 많은 우수한 개발자가 지적한 것처럼 상태를 변경하는 것은 잘못되었습니다!

이것을 알아내는 데 시간이 걸렸습니다. 위는 작동하지만 React의 힘을 빼앗습니다. 예를 들어 componentDidUpdate직접 수정 되었기 때문에이 업데이트를 업데이트로 볼 수 없습니다.

그래서에게 올바른 방법은 다음과 같습니다

handleChange = (e) => {
    this.setState(prevState => ({
        items: {
            ...prevState.items,
            [prevState.items[1].name]: e.target.value,
        },
    }));
};

이 스레드에는 많은 잘못된 정보가 있으므로 도우미 라이브러리없이 수행 할 수있는 방법은 다음과 같습니다.

handleChange: function (e) {
    // 1. Make a shallow copy of the items
    let items = [...this.state.items];
    // 2. Make a shallow copy of the item you want to mutate
    let item = {...items[1]};
    // 3. Replace the property you're intested in
    item.name = 'newName';
    // 4. Put it back into our array. N.B. we *are* mutating the array here, but that's why we made a copy first
    items[1] = item;
    // 5. Set the state to our new copy
    this.setState({items});
},

원하는 경우 2 단계와 3 단계를 결합 할 수 있습니다.

let item = {
    ...items[1],
    name: 'newName'
}

또는 한 줄로 모든 것을 할 수 있습니다.

this.setState(({items}) => ({
    items: [
        ...items.slice(0,1),
        {
            ...items[1],
            name: 'newName',
        },
        ...items.slice(2)
    ]
}));

참고 : items배열을 만들었습니다 . OP는 개체를 사용했습니다. 그러나 개념은 동일합니다.


터미널 / 콘솔에서 진행중인 작업을 확인할 수 있습니다.

❯ node
> items = [{name:'foo'},{name:'bar'},{name:'baz'}]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> clone = [...items]
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ]
> item1 = {...clone[1]}
{ name: 'bar' }
> item1.name = 'bacon'
'bacon'
> clone[1] = item1
{ name: 'bacon' }
> clone
[ { name: 'foo' }, { name: 'bacon' }, { name: 'baz' } ]
> items
[ { name: 'foo' }, { name: 'bar' }, { name: 'baz' } ] // good! we didn't mutate `items`
> items === clone
false // these are different objects
> items[0] === clone[0]
true // we don't need to clone items 0 and 2 because we're not mutating them (efficiency gains!)
> items[1] === clone[1]
false // this guy we copied

(가)의 상태 반응에 깊이 중첩 된 객체 / 변수를 수정하려면, 일반적으로 세 가지 방법이 사용된다 : 바닐라 JS Object.assign, 불변성 헬퍼cloneDeep에서 Lodash은 . 이것을 달성하기 위해 덜 인기있는 다른 타사 라이브러리도 많이 있지만이 답변에서는이 세 가지 옵션 만 다룰 것입니다. 또한 배열 확산과 같은 바닐라 JavaScript 이외의 다른 방법을 사용하는 방법이 있지만 (예 : @mpen의 답변 참조) 직관적이지 않고 사용하기 쉽고 모든 상태 조작 상황을 처리 할 수 ​​없습니다.

답변에 대한 최고 투표 의견에서 셀 수없이 많은 시간이 지적 되었 듯이, 저자는 직접적인 국가 변이를 제안하지만, 그렇게하지 마십시오 . 이것은 유비쿼터스 리 액트 안티 패턴으로 필연적으로 원치 않는 결과를 초래합니다. 올바른 방법을 배우십시오.

널리 사용되는 세 가지 방법을 비교해 봅시다.

참고 :이 예제는 클래스 구성 요소 및 수명주기와 함께 이전 방식을 사용하여 상태를 업데이트합니다. 최신 후크 API에는 다른 구문이 있지만 아이디어는 여전히 동일합니다. 이는이 모든 예제가 여전히 유효 함을 의미합니다.

이 상태 구조는 다음과 같습니다.

state = {
    foo: {
        bar: 'initial value'
    }
}

1. 바닐라 JavaScript의 Object.assign

(...essential imports)
class App extends Component {

    state = {
        foo: {
            bar: 'initial value'
        }
    }

    componentDidMount() {

        console.log(this.state.foo.bar) // initial value

        const foo = Object.assign({}, this.state.foo, { bar: 'further value' })

        console.log(this.state.foo.bar) // initial value

        this.setState({ foo }, () => {
            console.log(this.state.foo.bar) // further value
        })
    }
    (...rest of code)

것을 명심 Object.assign이 깊은 복제를 수행하지 않습니다 , 이후 그것은 단지 복사 속성 값 과의 그 이유는 그것이라고 무엇을 복사 얕은을 (주석 참조).

이것이 작동하려면이 객체의 최상위 항목 ( state.foo) 만 조작해야합니다 . 그리고 그 값 ( state.foo.bar)은 기본 (문자열, 숫자, 부울)이어야합니다.

이 예제에서, 우리는 새로운 상수 (만들 const foo...사용) Object.assign(빈 객체를 생성하는 {}), 복사 state.foo(객체 { bar: 'initial value' }그것으로) 한 후 사본을 다른 객체 { bar: 'further value' }위에. 결국 새로 생성 된 foo상수는 속성이 재정 의 된 { bar: 'further value' }이후의 값을 유지합니다 bar. 이것은 foo상태 객체에 연결되지 않은 새로운 객체이므로 필요에 따라 변경 될 수 있으며 상태는 변경되지 않습니다.

마지막 부분은 setState()setter 를 사용 state.foo하여 상태 의 원본 을 새로 만든 foo객체 로 바꾸는 것 입니다.

이제 우리가보다 깊은 상태를 가지고 있다고 상상해보십시오 state = { foo: { bar: { baz: 'initial value' } } }. foo객체 를 만들어 foo상태 내용으로 채울 수는 있지만 너무 깊게 중첩되어 있으므로 새로 만든 객체에 값을 Object.assign복사 할 수 없습니다 . 위의 예와 같이 여전히 복사 할 수는 있지만 프리미티브가 아닌 객체이므로 참조 가 대신 복사 되므로 상태에 직접 연결된 로컬 객체로 끝납니다 . 즉,이 경우 로컬에서 생성 된 모든 돌연변이가 실제로 동일한 것을 가리 키기 때문에 객체에 영향을 미칩니다 .bazfoobazbarstate.foo.barfoofoostate.foo

Object.assign 따라서 가장 기본적인 멤버가 기본 유형의 값을 보유하는 비교적 단순한 1 단계 딥 스테이트 구조 인 경우에만 작동합니다.

업데이트해야하는 더 깊은 개체 (2 단계 이상)가있는 경우을 사용하지 마십시오 Object.assign. 상태를 직접 변경할 위험이 있습니다.

2. Lodash의 cloneDeep

(...essential imports)
import cloneDeep from 'lodash.clonedeep'

class App extends Component {

    state = {
        foo: {
            bar: 'initial value'
        }
    }

    componentDidMount() {

        console.log(this.state.foo.bar) // initial value

        const foo = cloneDeep(this.state.foo)

        foo.bar = 'further value'  

        console.log(this.state.foo.bar) // initial value

        this.setState({ foo }, () => {
            console.log(this.state.foo.bar) // further value
        })
    }
    (...rest of code)

Lodash의 cloneDeep 은 사용하기가 더 간단합니다. 딥 클로닝을 수행 하므로 다중 레벨 객체 또는 배열이 상당히 복잡한 상태 인 경우 강력한 옵션입니다. cloneDeep()최상위 상태 속성 만으로 원하는 부분을 복제 한 setState()후 상태로 되돌릴 수 있습니다.

3. 불변 헬퍼

(...essential imports)
import update from 'immutability-helper'

class App extends Component {

    state = {
        foo: {
            bar: 'initial value'
        }
    };

    componentDidMount() {

        console.log(this.state.foo.bar) // initial value     

        const foo = update(this.state.foo, { bar: { $set: 'further value' } })  

        console.log(this.state.foo.bar) // initial value

        this.setState({ foo }, () => {
            console.log(this.state.foo.bar) // further value
        });
    }    
    (...rest of code)

immutability-helper takes it to the whole new level, and the cool thing about it is that it can not only $set values to state items, but also $push, $splice, $merge (etc.) them. Here is a list of commands available.

Side notes

Again, keep in mind, that this.setState() only modifies the first-level properties of the state object (foo property in this case), not the deeply nested (foo.bar). If it behaved another way, this question wouldn't exist.

And by the way, this.setState({ foo }) is just a shorthand for this.setState({ foo: foo }). And () => { console.log(this.state.foo.bar) } after the { foo } is a callback which gets executed immediately after setState have set the state. Convenient, if you need to do some things after it did its job (in our case to display the state immediately after it was set).

프로젝트에 적합한 것은 무엇입니까?

외부 의존성을 원하지 않거나 사용할 수없고 간단한 상태 구조를 가지고 있다면을 고수하십시오 Object.assign.

거대하고 복잡한 상태조작하는 경우 Lodash cloneDeep가 현명한 선택입니다.

고급 기능 이 필요한 경우 , 즉 상태 구조가 복잡하고 모든 종류의 작업을 수행해야하는 immutability-helper경우 상태 조작에 사용할 수있는 매우 고급 도구입니다.


나는 같은 문제가 있었다. 작동하는 간단한 해결책이 있습니다!

const newItems = [...this.state.items];
newItems[item] = value;
this.setState({ items:newItems });

먼저 원하는 항목을 가져 와서 해당 개체에서 원하는 것을 변경 한 다음 상태로 다시 설정하십시오. getInitialState키가있는 객체를 사용하면 객체 만 전달하여 상태를 사용하는 방법이 훨씬 쉬워집니다.

handleChange: function (e) {
   item = this.state.items[1];
   item.name = 'newName';
   items[1] = item;

   this.setState({items: items});
}

setState 의 React 문서에 따르면 Object.assign다른 답변에서 제안한대로 사용 하는 것이 이상적이지 않습니다. setState의 비동기 동작 특성으로 인해이 기술을 사용한 후속 호출은 이전 호출을 무시하여 원하지 않는 결과를 초래할 수 있습니다.

대신 React 문서 setState는 이전 상태에서 작동 하는 업데이터 양식을 사용하는 것이 좋습니다 . React가 상태 불변성을 보존해야하기 때문에 배열이나 객체 업데이트 할 때 새로운 배열이나 객체반환해야합니다 . ES6 구문의 spread 연산자를 사용하여 배열을 얕게 복사하고 주어진 배열 인덱스에서 객체의 속성을 만들거나 업데이트하면 다음과 같습니다.

this.setState(prevState => {
    const newItems = [...prevState.items];
    newItems[index].name = newName;
    return {items: newItems};
})

상태를 변경하지 마십시오. 예기치 않은 결과가 발생할 수 있습니다. 나는 나의 교훈을 배웠다! 항상 복사 / 복제 작업을 수행 Object.assign()하는 것이 좋습니다.

item = Object.assign({}, this.state.items[1], {name: 'newName'});
items[1] = item;
this.setState({items: items});

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


정말 간단합니다.

First pull the entire items object from state, updated the part of the items object as desired, and put the entire items object back in state via setState.

handleChange: function (e) {
  items = Object.assign(this.state.items); // Pull the entire items object out. Using object.assign is a good idea for objects.
  items[1].name = 'newName'; // update the items object as needed
  this.setState({ items }); // Put back in state
}

Mutation free:

// given a state
state = {items: [{name: 'Fred', value: 1}, {name: 'Wilma', value: 2}]}

// This will work without mutation as it clones the modified item in the map:
this.state.items
   .map(item => item.name === 'Fred' ? {...item, ...{value: 3}} : item)

this.setState(newItems)

Use the event on handleChange to figure out the element that has changed and then update it. For that you might need to change some property to identify it and update it.

See fiddle https://jsfiddle.net/69z2wepo/6164/


I would move the function handle change and add an index parameter

handleChange: function (index) {
    var items = this.state.items;
    items[index].name = 'newName';
    this.setState({items: items});
},

to the Dynamic form component and pass it to the PopulateAtCheckboxes component as a prop. As you loop over your items you can include an additional counter (called index in the code below) to be passed along to the handle change as shown below

{ Object.keys(this.state.items).map(function (key, index) {
var item = _this.state.items[key];
var boundHandleChange = _this.handleChange.bind(_this, index);
  return (
    <div>
        <PopulateAtCheckboxes this={this}
            checked={item.populate_at} id={key} 
            handleChange={boundHandleChange}
            populate_at={data.populate_at} />
    </div>
);
}, this)}

Finally you can call your change listener as shown below here

<input type="radio" name={'populate_at'+this.props.id} value={value} onChange={this.props.handleChange} checked={this.props.checked == value} ref="populate-at"/>

If you need to change only part of the Array, You've a react component with state set to.

state = {items: [{name: 'red-one', value: 100}, {name: 'green-one', value: 999}]}

It's best to update the red-one in the Array as follows:

const itemIndex = this.state.items.findIndex(i=> i.name === 'red-one');
const newItems = [
   this.state.items.slice(0, itemIndex),
   {name: 'red-one', value: 666},
   this.state.items.slice(itemIndex)
]

this.setState(newItems)

As none of the above options was ideal to me I ended up using map:

this.setState({items: this.state.items.map((item,idx)=> idx!==1 ?item :{...item,name:'new_name'}) })

or if you have a dynamically generated list and you don't know the index but just have the key or id:

let ItemsCopy = []
let x = this.state.Items.map((entry) =>{

    if(entry.id == 'theIDYoureLookingFor')
    {
        entry.PropertyToChange = 'NewProperty'
    }

    ItemsCopy.push(entry)
})


this.setState({Items:ItemsCopy});

Try with code:

this.state.items[1] = 'new value';
var cloneObj = Object.assign({}, this.state.items);

this.setState({items: cloneObj });

Following piece of code went easy on my dull brain. Removing the object and replacing with the updated one

    var udpateditem = this.state.items.find(function(item) { 
                   return item.name == "field_1" });
    udpateditem.name= "New updated name"                       
    this.setState(prevState => ({                                   
    items:prevState.dl_name_template.filter(function(item) { 
                                    return item.name !== "field_1"}).concat(udpateditem)
    }));

How about creating another component(for object that needs to go into the array) and pass the following as props?

  1. component index - index will be used to create/update in array.
  2. set function - This function put data into the array based on the component index.
<SubObjectForm setData={this.setSubObjectData}                                                            objectIndex={index}/>

Here {index} can be passed in based on position where this SubObjectForm is used.

and setSubObjectData can be something like this.

 setSubObjectData: function(index, data){
      var arrayFromParentObject= <retrieve from props or state>;
      var objectInArray= arrayFromParentObject.array[index];
      arrayFromParentObject.array[index] = Object.assign(objectInArray, data);
 }

In SubObjectForm, this.props.setData can be called on data change as given below.

<input type="text" name="name" onChange={(e) => this.props.setData(this.props.objectIndex,{name: e.target.value})}/>

Try this it will definetly work,other case i tried but didn't work

import _ from 'lodash';

this.state.var_name  = _.assign(this.state.var_name, {
   obj_prop: 'changed_value',
});

참고URL : https://stackoverflow.com/questions/29537299/react-how-do-i-update-state-item1-on-setstate-with-jsfiddle

반응형