Я пытаюсь организовать свое состояние, используя вложенное свойство, например:

this.state = {
   someProperty: {
      flag:true
   }
}

Но состояние обновления вот такое,

this.setState({ someProperty.flag: false });

не работает. Как это сделать правильно?

Ответы (32)

Чтобы setState для вложенного объекта, вы можете следовать приведенному ниже подходу, поскольку я думаю, что setState не обрабатывает вложенные обновления.

var someProperty = {...this.state.someProperty}
someProperty.flag = true;
this.setState({someProperty})

Идея состоит в том, чтобы создать фиктивный объект, выполнить над ним операции и затем заменить состояние компонента обновленным объектом

Теперь оператор распространения создает только одну вложенную копию объекта уровня. Если ваше состояние сильно вложено, например:

this.state = {
   someProperty: {
      someOtherProperty: {
          anotherProperty: {
             flag: true
          }
          ..
      }
      ...
   }
   ...
}

Вы можете установитьState, используя оператор распространения на каждом уровне, например

this.setState(prevState => ({
    ...prevState,
    someProperty: {
        ...prevState.someProperty,
        someOtherProperty: {
            ...prevState.someProperty.someOtherProperty, 
            anotherProperty: {
               ...prevState.someProperty.someOtherProperty.anotherProperty,
               flag: false
            }
        }
    }
}))

Однако приведенный выше синтаксис становится все уродливее, поскольку состояние становится все более и более вложенным, и поэтому я рекомендую вам использовать пакет неизменяемости для обновления состояния.

См. этот ответ о том, как обновить состояние с помощью неизменяемости-помощника.

Не уверен, что это технически правильно в соответствии со стандартами фреймворка, но иногда вам просто нужно обновить вложенные объекты. Вот мое решение с использованием хуков.

setInputState ({
                ... inputState,
                [parentKey]: {... inputState [parentKey], [childKey]: значение},
            });

Я использовал это решение.

Если у вас есть такое вложенное состояние:

   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 с прослушивателем событий


Иногда прямые ответы не самые лучшие :)

Краткая версия:

этот код

this.state = {
    someProperty: {
        flag: true
    }
}

следует упростить как что-то вроде

this.state = {
    somePropertyFlag: true
}

Длинная версия:

На данный момент вам не нужно работать с вложенным состоянием в React. Потому что React не ориентирован на работу с вложенными состояниями и все предлагаемые здесь решения выглядят как хаки. Они не используют фреймворк, а борются с ним. Они предлагают написать не очень понятный код для сомнительной цели группировки некоторых свойств. Так что они очень интересны как ответ на вызов, но практически бесполезны.

Представим себе следующее состояние:

{
    parent: {
        child1: 'value 1',
        child2: 'value 2',
        ...
        child100: 'value 100'
    }
}

Что произойдет, если вы измените только значение child1? React не будет повторно отображать представление, потому что он использует неглубокое сравнение и обнаружит, что свойство parent не изменилось. Кстати, изменение объекта состояния напрямую в целом считается плохой практикой.

Значит, вам нужно заново создать весь родительский объект. Но в этом случае мы столкнемся с другой проблемой. React будет думать, что все дочерние элементы изменили свои ценности, и заново отрендерит их все. Конечно, для производительности это не очень хорошо.

Эту проблему все еще можно решить, написав сложную логику в shouldComponentUpdate (), но я бы предпочел остановиться на этом и использовать простое решение из краткой версии.

Заявление об ограничении ответственности

Вложенное состояние в React неверно спроектировано

Прочтите этот отличный ответ.

Reasoning behind this answer:

React's setState is just a built-in convenience, but you soon realise that it has its limits. Using custom properties and intelligent use of forceUpdate gives you much more. eg:

class MyClass extends React.Component {
    myState = someObject
    inputValue = 42
...

MobX, for example, ditches state completely and uses custom observable properties.
Use Observables instead of state in React components.


ответ на ваши невзгоды - см. Пример здесь

Существует другой более короткий способ обновить любое вложенное свойство.

this.setState(state => {
  state.nested.flag = false
  state.another.deep.prop = true
  return state
})

На одной строке

 this.setState(state => (state.nested.flag = false, state))

примечание: это оператор запятой ~ MDN, посмотрите его в действии здесь (песочница).

Это похоже на (хотя это не меняет ссылку на состояние)

this.state.nested.flag = false
this.forceUpdate()

Для тонкой разницы в этом контексте между forceUpdate и setState см. Связанный пример и песочница.

Конечно, это нарушает некоторые основные принципы, так как состояние должно быть доступно только для чтения, но, поскольку вы немедленно отбрасываете старое состояние и заменяете его новым состоянием, это полностью нормально.

Предупреждение

Even though the component containing the state will update and rerender properly (except this gotcha), the props will fail to propagate to children (see Spymaster's comment below). Only use this technique if you know what you are doing.

Например, вы можете передать измененную плоскую опору, которая легко обновляется и передается.

render(
  //some complex render with your nested state
  
)

Теперь, хотя ссылка на complexNestedProp не изменилась (shouldComponentUpdate)

this.props.complexNestedProp === nextProps.complexNestedProp

компонент будет повторно отображать каждый раз при обновлении родительского компонента, что происходит после вызова this.setState или this.forceUpdate в родительском.

Эффекты мутации состояния песочница

Использование вложенного состояния и непосредственное изменение состояния опасно, потому что разные объекты могут содержать (намеренно или нет) разные (старые) ссылки на состояние и не обязательно знать, когда следует update (например, при использовании PureComponent или если shouldComponentUpdate реализовано для возврата false) OR предназначены для отображения старых данных, как в пример ниже.

Представьте себе временную шкалу, которая должна отображать исторические данные, изменение данных под рукой приведет к неожиданному поведению, так как это также изменит предыдущие элементы.

state-flow state-flow-nested

В любом случае, здесь вы можете увидеть, что Nested PureChildClass не перерисовывается из-за того, что пропсы не могут распространяться.

Я знаю, что это старый вопрос, но все же хотел рассказать, как я этого добился. Предполагаемое состояние в конструкторе выглядит так:

  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      user: {
        email: ""
      },
      organization: {
        name: ""
      }
    };

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

Моя handleChange функция выглядит так:

  handleChange(e) {
    const names = e.target.name.split(".");
    const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
    this.setState((state) => {
      state[names[0]][names[1]] = value;
      return {[names[0]]: state[names[0]]};
    });
  }

И убедитесь, что вы присвоили входам соответствующее имя:




Создать копию состояния:

let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))

внести изменения в этот объект:

someProperty.flag = "false"

теперь обновите состояние

this.setState({someProperty})

Я очень серьезно отношусь к озабоченности , уже высказанной по поводу создания полной копии состояния вашего компонента. С учетом сказанного, я настоятельно рекомендую Immer.

import produce from 'immer';

 produce(this.state, s => { s.form.username = e.target.value }) } />

Это должно работать для React.PureComponent (т.е. неглубоких сравнений состояний с помощью React), поскольку Immer умно использует прокси-объект для эффективного копирования произвольно глубокого дерева состояний. Immer также более безопасен по сравнению с библиотеками, такими как Immutability Helper, и идеально подходит как для пользователей Javascript, так и для Typescript.


Машинопись служебная функция

function setStateDeep(comp: React.Component, fn: (s: 
Draft>) => any) {
  comp.setState(produce(comp.state, s => { fn(s); }))
}

onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}

Это мое начальное состояние

    const initialStateInput = {
        cabeceraFamilia: {
            familia: '',
            direccion: '',
            telefonos: '',
            email: ''
        },
        motivoConsulta: '',
        fechaHora: '',
        corresponsables: [],
    }

Хук или его можно заменить на состояние (компонент класса)

const [infoAgendamiento, setInfoAgendamiento] = useState(initialStateInput);

Метод для handleChange

const actualizarState = e => {
    const nameObjects = e.target.name.split('.');
    const newState = setStateNested(infoAgendamiento, nameObjects, e.target.value);
    setInfoAgendamiento({...newState});
};

Метод установки состояния с вложенными состояниями

const setStateNested = (state, nameObjects, value) => {
    let i = 0;
    let operativeState = state;
    if(nameObjects.length > 1){
        for (i = 0; i < nameObjects.length - 1; i++) {
            operativeState = operativeState[nameObjects[i]];
        }
    }
    operativeState[nameObjects[i]] = value;
    return state;
}

Наконец, это вход, который я использую


Я делаю вложенные обновления с уменьшить поиск:

Пример:

Вложенные переменные в состоянии:

state = {
    coords: {
        x: 0,
        y: 0,
        z: 0
    }
}

Функция:

handleChange = nestedAttr => event => {
  const { target: { value } } = event;
  const attrs = nestedAttr.split('.');

  let stateVar = this.state[attrs[0]];
  if(attrs.length>1)
    attrs.reduce((a,b,index,arr)=>{
      if(index==arr.length-1)
        a[b] = value;
      else if(a[b]!=null)
        return a[b]
      else
        return a;
    },stateVar);
  else
    stateVar = value;

  this.setState({[attrs[0]]: stateVar})
}

Используйте:


Хотя вы спрашивали о состоянии компонента React на основе классов, та же проблема существует с хуком useState. Еще хуже: перехватчик useState не принимает частичные обновления. Таким образом, этот вопрос стал очень актуальным, когда был введен хук useState.

Я решил опубликовать следующий ответ, чтобы убедиться, что вопрос охватывает более современные сценарии, в которых используется перехватчик useState:

Если у вас есть:

const [state, setState] = useState({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })

вы можете установить вложенное свойство путем клонирования текущего и исправления требуемых сегментов данных, например:

setState(current => { ...current, someProperty: { ...current.someProperty, flag: false } });

Или вы можете использовать библиотеку Immer, чтобы упростить клонирование и исправление объекта.

Или вы можете использовать библиотеку Hookstate (отказ от ответственности: я являюсь автором), чтобы просто полностью управлять сложными (локальными и глобальными) данными состояния и повысить производительность (читайте: не беспокоиться об оптимизации рендеринга ):

import { useStateLink } from '@hookstate/core' 
const state = useStateLink({ someProperty: { flag: true, otherNestedProp: 1 }, otherProp: 2 })

получить поле для рендеринга:

state.nested.someProperty.nested.flag.get()
// or 
state.get().someProperty.flag

установить вложенное поле:

state.nested.someProperty.nested.flag.set(false)

Вот пример Hookstate, где состояние глубоко / рекурсивно вложено в древовидную структуру данных.

Это явно не лучший и не лучший способ, но, на мой взгляд, он чище:

this.state.hugeNestedObject = hugeNestedObject; 
this.state.anotherHugeNestedObject = anotherHugeNestedObject; 

this.setState({})

Однако React сам должен перебирать вложенные объекты и соответственно обновлять состояние и DOM, чего еще нет.

вы можете сделать это с объектным растяжением код:

 this.setState((state)=>({ someProperty:{...state.someProperty,flag:false}})

это будет работать для более вложенного свойства

Если вы используете formik в своем проекте, у него есть простой способ справиться с этим. Вот самый простой способ сделать с формиком.

Сначала установите начальные значения внутри атрибута formik initivalues ​​или в response. состояние

Здесь начальные значения определены в состоянии реакции

   state = { 
     data: {
        fy: {
            active: "N"
        }
     }
   }

определите выше initialValues ​​для поля formik внутри formik initiValues ​​ attribute

 {...your actions goes here}}
>
{({ isSubmitting }) => (
  
{ const value = e.target.checked; if(value) setFieldValue('fy.active', 'Y') else setFieldValue('fy.active', 'N') }}/> )}

Создайте консоль для проверки состояния, обновленного в string вместо booleanformik setFieldValue, чтобы установить состояние или перейти с помощью инструмента отладчика реакции для см. изменения в значениях состояния формика.

Я вижу, что все дали решение об обновлении состояния компонента на основе класса, которое ожидается, потому что он просил об этом, но я пытаюсь дать такое же решение для ловушки.

const [state, setState] = useState({
    state1: false,
    state2: 'lorem ipsum'
})

Теперь, если вы хотите изменить ключ вложенного объекта state1, только тогда вы можете выполнить любое из следующих действий:

Процесс 1

let oldState = state;
oldState.state1 = истина
setState ({... oldState);

Процесс 2

setState (prevState => ({
    ... prevState,
    state1: true
}))

Я больше всего предпочитаю процесс 2.

stateUpdate = () => {
    let obj = this.state;
    if(this.props.v12_data.values.email) {
      obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
    }
    this.setState(obj)
}

Вы должны передать новое состояние в setState. ссылка на новое состояние должна отличаться от старого состояния.

So try this:

this.setState({
    ...this.state,
    someProperty: {...this.state.someProperty, flag: true},
})
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);

попробуйте этот код:

this.setState({ someProperty: {flag: false} });

Есть другой вариант, и он работает, если в списке объектов несколько элементов: скопируйте объект, используя this.state.Obj, в переменную (скажем, temp), используйте метод filter (), чтобы пройти через объект и захватить конкретный элемент, который вы хотите преобразовать в один объект (назовите его updateObj), а оставшийся список объектов - в другой объект (назовите его restObj). Теперь отредактируйте содержимое объекта, который вы хотите обновить, создав новый элемент (скажем, newItem). Затем вызовите this.setUpdate () и используйте операторы распространения для передачи нового списка объектов родительскому объекту.

this.state = {someProperty: { flag:true, }}


var temp=[...this.state.someProperty]
var restObj = temp.filter((item) => item.flag !== true);
var updateObj = temp.filter((item) => item.flag === true);

var newItem = {
  flag: false
};
this.setState({ someProperty: [...restObj, newItem] });

Чтобы записать в одну строку

this.setState({ someProperty: { ...this.state.someProperty, flag: false} });

Что-то вроде этого может хватить,

const isObject = (thing) => {
    if(thing && 
        typeof thing === 'object' &&
        typeof thing !== null
        && !(Array.isArray(thing))
    ){
        return true;
    }
    return false;
}

/*
  Call with an array containing the path to the property you want to access
  And the current component/redux state.

  For example if we want to update `hello` within the following obj
  const obj = {
     somePrimitive:false,
     someNestedObj:{
        hello:1
     }
  }

  we would do :
  //clone the object
  const cloned = clone(['someNestedObj','hello'],obj)
  //Set the new value
  cloned.someNestedObj.hello = 5;

*/
const clone = (arr, state) => {
    let clonedObj = {...state}
    const originalObj = clonedObj;
    arr.forEach(property => {
        if(!(property in clonedObj)){
            throw new Error('State missing property')
        }

        if(isObject(clonedObj[property])){
            clonedObj[property] = {...originalObj[property]};
            clonedObj = clonedObj[property];
        }
    })
    return originalObj;
}

const nestedObj = {
    someProperty:true,
    someNestedObj:{
        someOtherProperty:true
    }
}

const clonedObj = clone(['someProperty'], nestedObj);
console.log(clonedObj === nestedObj) //returns false
console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true

console.log()
const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
console.log(clonedObj2 === nestedObj) // returns false
console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
//returns true (doesn't attempt to clone because its primitive type)
console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty) 

Два других варианта, которые еще не упомянуты:

  1. Если у вас есть глубоко вложенное состояние, подумайте, можете ли вы реструктурировать дочерние объекты, чтобы они располагались в корне. Это упрощает обновление данных.
  2. Существует множество удобных библиотек для обработки неизменяемого состояния , перечисленных в документации Redux. Я рекомендую Immer, поскольку он позволяет писать код изменчивым образом, но выполняет необходимое клонирование за кулисами. Он также замораживает получившийся объект, чтобы вы не могли случайно изменить его позже.

Вот вариант первого ответа, данного в этой ветке, который не требует дополнительных пакетов, библиотек или специальных функций.

state = {
  someProperty: {
    flag: 'string'
  }
}

handleChange = (value) => {
  const newState = {...this.state.someProperty, flag: value}
  this.setState({ someProperty: newState })
}

Чтобы установить состояние определенного вложенного поля, вы установили весь объект. Я сделал это, создав переменную newState и распространив в нее содержимое текущего состояния first, используя ES2015 оператор распространения. Затем я заменил значение this.state.flag новым значением (поскольку я установил flag: value после Я распространяю текущее состояние на объект , поле flag в текущем состоянии отменяется). Затем я просто устанавливаю состояние someProperty для моего объекта newState.

Я обнаружил, что это работает для меня, имея форму проекта в моем случае, где, например, у вас есть идентификатор и имя, и я бы предпочел поддерживать состояние для вложенного проекта.

return (
  

Project Details

this.setState({ project: {...this.state.project, id: event.target.value}})} /> this.setState({ project: {...this.state.project, name: event.target.value}})} />
)

Сообщите мне!

Используйте это для управления множественным вводом и динамического вложенного имени




код очень читаемый

обработчик

handleInputChange = (event) => {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;
        const newState = { ...this.state.someProperty, [name]: value }
        this.setState({ someProperty: newState })
    }

Если вы хотите установить состояние динамически


следующий пример устанавливает состояние формы динамически, где каждый ключ в состоянии является объектом

 onChange(e:React.ChangeEvent) {
    this.setState({ [e.target.name]: { ...this.state[e.target.name], value: e.target.value } });
  }

Есть много библиотек, помогающих с этим. Например, используя неизменяемость-помощник:

import update from 'immutability-helper';

const newState = update(this.state, {
  someProperty: {flag: {$set: false}},
};
this.setState(newState);

Использование lodash / fp set:

import {set} from 'lodash/fp';

const newState = set(["someProperty", "flag"], false, this.state);

Использование lodash / fp merge:

import {merge} from 'lodash/fp';

const newState = merge(this.state, {
  someProperty: {flag: false},
});

Мы используем Immer https://github.com/mweststrate/immer для решения подобных проблем.

Просто заменил этот код в одном из наших компонентов

this.setState(prevState => ({
   ...prevState,
        preferences: {
            ...prevState.preferences,
            [key]: newValue
        }
}));

С этим

import produce from 'immer';

this.setState(produce(draft => {
    draft.preferences[key] = newValue;
}));

С иммером вы обрабатываете свое состояние как «нормальный объект». Волшебство происходит за сценой с прокси-объектами.

Хотя на самом деле вложенность - это не совсем то, как вы должны относиться к состоянию компонента, иногда для чего-то более простого для одноуровневой вложенности.

Для такого состояния

state = {
 contact: {
  phone: '888-888-8888',
  email: 'test@test.com'
 }
 address: {
  street:''
 },
 occupation: {
 }
}

Повторно используемый метод, который я использовал, будет выглядеть так.

handleChange = (obj) => e => {
  let x = this.state[obj];
  x[e.target.name] = e.target.value;
  this.setState({ [obj]: x });
};

затем просто передавая имя объекта для каждого вложения, которое вы хотите адресовать ...


Если вы используете ES2015, у вас есть доступ к Object.assign. Вы можете использовать его следующим образом для обновления вложенного объекта.

this.setState({
  someProperty: Object.assign({}, this.state.someProperty, {flag: false})
});

Вы объединяете обновленные свойства с существующими и используете возвращенный объект для обновления состояния.

Edit: Добавлен пустой объект в качестве цели для функции assign, чтобы убедиться, что состояние не изменяется напрямую, как указал carkod.

Чтобы сделать вещи общими, я работал над ответами @ ShubhamKhatri и @ Qwerty.

объект состояния

this.state = {
  name: '',
  grandParent: {
    parent1: {
      child: ''
    },
    parent2: {
      child: ''
    }
  }
};

элементы управления вводом




метод updateState

setState как ответ @ ShubhamKhatri

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const oldstate = this.state;
  const newstate = { ...oldstate };
  let newStateLevel = newstate;
  let oldStateLevel = oldstate;

  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      newStateLevel[path[i]] = event.target.value;
    } else {
      newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
      oldStateLevel = oldStateLevel[path[i]];
      newStateLevel = newStateLevel[path[i]];
    }
  }
  this.setState(newstate);
}

setState как ответ @ Qwerty

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const state = { ...this.state };
  let ref = state;
  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      ref[path[i]] = event.target.value;
    } else {
      ref = ref[path[i]];
    }
  }
  this.setState(state);
}

Примечание: эти методы выше не работают для массивов

2022 WebDevInsider