Как правильно обновить состояние вложенного объекта в React with Hooks?
export Example = () => {
const [exampleState, setExampleState] = useState(
{masterField: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b"
fieldTwoTwo: "c"
}
}
})
Как можно использовать setExampleState для обновления exampleState до a (добавление поля)?
const a = {
masterField: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}
}
b (изменение значений)?
const b = {masterField: {
fieldOne: "e",
fieldTwo: {
fieldTwoOne: "f"
fieldTwoTwo: "g"
}
}
})
@Justcode Для первого примера да, для второго просто изменение существующего объекта
onValueChange = {() => setSelection ({... пред, id_1: true})}



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете передать новое значение следующим образом:
setExampleState({...exampleState, masterField2: {
fieldOne: "a",
fieldTwo: {
fieldTwoOne: "b",
fieldTwoTwo: "c"
}
},
})
Отлично, это ответы на часть а! вы знаете лучший метод для части b?
изменение также такое же, как только предыдущий masterField вместо masterField2setExampleState({...exampleState, masterField: {// новые значения}
Помните о неглубоком копировании при использовании оператора распространения. См. Например: stackoverflow.com/questions/43638938/…
Если вы используете то же состояние с его Диспетчером, вам следует использовать функцию. setExampleState( exampleState => ({...exampleState, masterField2: {...etc} }) );
Как правило, вы должны следить за глубоко вложенными объектами в состоянии React. Чтобы избежать неожиданного поведения, состояние должно обновляться постоянно. Когда у вас есть глубокие объекты, вы заканчиваете их глубоким клонированием для неизменности, что может быть довольно дорогостоящим в React. Почему?
После того, как вы глубоко клонируете состояние, React пересчитает и повторно отрендерит все, что зависит от переменных, даже если они не изменились!
Итак, прежде чем пытаться решить вашу проблему, подумайте, как вы можете сначала сгладить состояние. Как только вы это сделаете, вы найдете удобные инструменты, которые помогут работать с большими состояниями, такие как useReducer ().
Если вы подумали об этом, но все еще уверены, что вам нужно использовать глубоко вложенное дерево состояний, вы все равно можете использовать useState () с такими библиотеками, как immutable.js и Неизменяемость-помощник. Они упрощают обновление или клонирование глубоких объектов, не беспокоясь об изменчивости.
Вы также можете использовать Hookstate (отказ от ответственности: я являюсь автором) для управления сложными (локальными и глобальными) данными состояния без глубокого клонирования и без беспокойства о ненужных обновлениях - Hookstate сделает это за вас.
Я оставляю вам служебную функцию для неизменного обновления объектов
/**
* Inmutable update object
* @param {Object} oldObject Object to update
* @param {Object} updatedValues Object with new values
* @return {Object} New Object with updated values
*/
export const updateObject = (oldObject, updatedValues) => {
return {
...oldObject,
...updatedValues
};
};
Так что вы можете использовать это так
const MyComponent = props => {
const [orderForm, setOrderForm] = useState({
specialities: {
elementType: "select",
elementConfig: {
options: [],
label: "Specialities"
},
touched: false
}
});
// I want to update the options list, to fill a select element
// ---------- Update with fetched elements ---------- //
const updateSpecialitiesData = data => {
// Inmutably update elementConfig object. i.e label field is not modified
const updatedOptions = updateObject(
orderForm[formElementKey]["elementConfig"],
{
options: data
}
);
// Inmutably update the relevant element.
const updatedFormElement = updateObject(orderForm[formElementKey], {
touched: true,
elementConfig: updatedOptions
});
// Inmutably update the relevant element in the state.
const orderFormUpdated = updateObject(orderForm, {
[formElementKey]: updatedFormElement
});
setOrderForm(orderFormUpdated);
};
useEffect(() => {
// some code to fetch data
updateSpecialitiesData.current("specialities",fetchedData);
}, [updateSpecialitiesData]);
// More component code
}
Если нет, у вас здесь есть другие утилиты: https://es.reactjs.org/docs/update.html
Я опаздываю на вечеринку .. :)
@aseferov ответ работает очень хорошо, когда намерение состоит в том, чтобы повторно ввести всю структуру объекта. Однако, если цель / цель - обновить конкретное значение поля в объекте, я считаю, что приведенный ниже подход лучше.
ситуация:
const [infoData, setInfoData] = useState({
major: {
name: "John Doe",
age: "24",
sex: "M",
},
minor:{
id: 4,
collegeRegion: "south",
}
});
Обновление конкретной записи потребует возврата к предыдущему состоянию prevState.
Здесь:
setInfoData((prevState) => ({
...prevState,
major: {
...prevState.major,
name: "Tan Long",
}
}));
возможно
setInfoData((prevState) => ({
...prevState,
major: {
...prevState.major,
name: "Tan Long",
},
minor: {
...prevState.minor,
collegeRegion: "northEast"
}));
Надеюсь, это поможет любому, кто пытается решить подобную проблему.
У меня есть вопрос. Зачем нам нужно заключать функцию в круглые скобки? Я не уверен, что происходит под капотом. Вы случайно не знаете, или где я могу прочитать об этом больше?
@MichaelRamage Почему мы заключаем функцию в скобки: (): Чтобы просто ответить на этот вопрос; это потому, что setInfoData по своей природе имеет высокий уровень, то есть он может принимать другую функцию в качестве аргумента, благодаря мощности, предоставляемой Hook: useState. Я надеюсь, что эта статья внесет больше ясности в функции высшего порядка: sitepoint.com/higher-order-functions-javascript
Очень полезно, особенно для случая, когда вы пытаетесь обновить свойство, вложенное в несколько уровней, например: first.second.third - другие примеры охватывают first.second, но не first.second.third
Если кто ищет обновления хуков useState () для объект
- Through Input
const [state, setState] = useState({ fName: "", lName: "" });
const handleChangeEvent = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value = {state.fName}
type = "text"
onChange = {handleChangeEvent}
name = "fName"
/>
<input
value = {state.lName}
type = "text"
onChange = {handleChangeEvent}
name = "lName"
/>
***************************
- Through onSubmit or button click
setState(prevState => ({
...prevState,
fName: 'your updated value here'
}));
Зачем нужен обратный вызов?
@Yola Этот ответ дает лучшее объяснение, чем я.
Спасибо, Филип, это помогло мне - в моем случае у меня была форма с множеством полей ввода, поэтому я сохранил исходное состояние как объект, и я не смог обновить состояние объекта. Приведенный выше пост помог мне :)
const [projectGroupDetails, setProjectGroupDetails] = useState({
"projectGroupId": "",
"projectGroup": "DDD",
"project-id": "",
"appd-ui": "",
"appd-node": ""
});
const inputGroupChangeHandler = (event) => {
setProjectGroupDetails((prevState) => ({
...prevState,
[event.target.id]: event.target.value
}));
}
<Input
id = "projectGroupId"
labelText = "Project Group Id"
value = {projectGroupDetails.projectGroupId}
onChange = {inputGroupChangeHandler}
/>
function App() {
const [todos, setTodos] = useState([
{ id: 1, title: "Selectus aut autem", completed: false },
{ id: 2, title: "Luis ut nam facilis et officia qui", completed: false },
{ id: 3, title: "Fugiat veniam minus", completed: false },
{ id: 4, title: "Aet porro tempora", completed: true },
{ id: 5, title: "Laboriosam mollitia et enim quasi", completed: false }
]);
const changeInput = (e) => {todos.map(items => items.id === parseInt(e.target.value) && (items.completed = e.target.checked));
setTodos([...todos], todos);}
return (
<div className = "container">
{todos.map(items => {
return (
<div key = {items.id}>
<label>
<input type = "checkbox"
onChange = {changeInput}
value = {items.id}
checked = {items.completed} /> {items.title}</label>
</div>
)
})}
</div>
);
}
Изначально я использовал объект в useState, но затем перешел на хук useReducer для сложных случаев. Я почувствовал улучшение производительности при рефакторинге кода.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
Я уже реализовал такой хук для себя:
/**
* Same as useObjectState but uses useReducer instead of useState
* (better performance for complex cases)
* @param {*} PropsWithDefaultValues object with all needed props
* and their initial value
* @returns [state, setProp] state - the state object, setProp - dispatch
* changes one (given prop name & prop value) or multiple props (given an
* object { prop: value, ...}) in object state
*/
export function useObjectReducer(PropsWithDefaultValues) {
const [state, dispatch] = useReducer(reducer, PropsWithDefaultValues);
//newFieldsVal = {[field_name]: [field_value], ...}
function reducer(state, newFieldsVal) {
return { ...state, ...newFieldsVal };
}
return [
state,
(newFieldsVal, newVal) => {
if (typeof newVal !== "undefined") {
const tmp = {};
tmp[newFieldsVal] = newVal;
dispatch(tmp);
} else {
dispatch(newFieldsVal);
}
},
];
}
более связанные хуки.
Думаю, лучшее решение - Погружать. Это позволяет вам обновлять объект, как если бы вы напрямую изменяли поля (masterField.fieldOne.fieldx = 'abc'). Но реального объекта это, конечно, не изменит. Он собирает все обновления для чернового объекта и в конце выдает окончательный объект, который можно использовать для замены исходного объекта.
, сделайте это как в этом примере:
первое состояние создания объектов:
const [isSelected, setSelection] = useState([{ id_1: false }, { id_2: false }, { id_3: false }]);
затем измените их значение:
// if the id_1 is false make it true or return it false.
onValueChange = {() => isSelected.id_1 == false ? setSelection([{ ...isSelected, id_1: true }]) : setSelection([{ ...isSelected, id_1: false }])}
Вы должны использовать параметры Rest и синтаксис распространения (https://javascript.info/rest-parameters-spread) И установить функцию с preState в качестве аргумента setState.
Не работает (отсутствует функция)
[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState({
...state,
[key]: value
});
Работает!
[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState(prevState => ({
...prevState,
[key]: value
}));
Я думаю, что более элегантным решением будет создание обновленного объекта состояния с сохранением предыдущих значений состояния. Свойство Object, которое необходимо обновить, может быть представлено в виде массива примерно так:
import React,{useState, useEffect} from 'react'
export default function Home2(props) {
const [x, setX] = useState({name : '',add : {full : '', pin : '', d : { v : '' }}})
const handleClick = (e, type)=>{
let obj = {}
if (type.length > 1){
var z = {}
var z2 = x[type[0]]
type.forEach((val, idx)=>{
if (idx === type.length - 1){
z[val] = e.target.value
}
else if (idx > 0){
Object.assign(z , z2) /*{...z2 , [val]:{} }*/
z[val] = {}
z = z[val]
z2 = z2[val]
}else{
z = {...z2}
obj = z
}
})
}else obj = e.target.value
setX( { ...x , [type[0]] : obj } )
}
return (
<div>
<input value = {x.name} onChange = {e=>handleClick(e,["name"])}/>
<input value = {x.add.full} onChange = {e=>handleClick(e,["add","full"])} />
<input value = {x.add.pin} onChange = {e=>handleClick(e,["add","pin"])} /><br/>
<input value = {x.add.d.v} onChange = {e=>handleClick(e,["add","d","v"])} /><br/>
{x.name} <br/>
{x.add.full} <br/>
{x.add.pin} <br/>
{x.add.d.v}
</div>
)
}
Добавление и изменение обоих можно выполнить простым шагом. Я думаю, что это более стабильный и безопасный вариант, который не имеет неизменяемых или изменяемых зависимостей.
Вот как вы можете добавить новый объект
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
Предположим, вы хотите снова изменить объект masterField2. Может быть две ситуации. Вы хотите обновить весь объект или обновить определенный ключ объекта.
Обновить весь объект - Итак, здесь будет обновлено все значение ключа masterField2.
setExampleState(prevState => ({
...prevState,
masterField2: {
fieldOne: "c",
fieldTwo: {
fieldTwoOne: "d",
fieldTwoTwo: "e"
}
},
}))
Но что, если вы хотите изменить только ключ fieldTwoOne внутри объекта masterField2. Вы делаете следующее.
let oldMasterField2 = exampleState.masterField2
oldMasterField2.fieldTwo.fieldTwoOne = 'changed';
setExampleState(prevState => ({
...prevState,
masterField2: oldMasterField2
}))
Ваш объект, для которого вы хотите сделать состояние
let teams = {
team: [
{
name: "one",
id: "1"
},
]
}
Создание объекта "Состояние команд"
const [state, setState] = useState(teams);
Обновить состояние как это
setState((prevState)=>({...prevState,team:[
...prevState.team,
{
name: "two",
id: "2"
}
]}))
После обновления Состояние становится
{
team: [
{
name: "one",
id: "1"
},
{
name: "two",
id: "2"
}
]
}
Для рендеринга элементов в соответствии с текущим состоянием используйте функцию карты
{state.team.map((curr_team) => {
return (
<div>
<p>{curr_team.id}</p>
<p>{curr_team.name}</p>
</div>
)
})}
Для управления сложным состоянием можно использовать ловушку useReducer вместо useState. Для этого сначала инициализируйте состояние и функцию обновления следующим образом:
const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
(state, updates) => ({
...state,
...updates,
}),
initialState
);
А затем вы можете обновить состояние, передавая только частичные обновления, например:
updateState({ ocupation: "postman" })
вы имеете в виду добавление нового значения ключа объекта к существующему объекту?