React Hooks useState () с объектом

100

Каков правильный способ обновления состояния вложенного объекта в 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 Для первого примера да, для второго просто изменение существующего объекта
isaacsultan

Ответы:

107

Вы можете передать новое значение следующим образом

  setExampleState({...exampleState,  masterField2: {
        fieldOne: "c",
        fieldTwo: {
           fieldTwoOne: "d",
           fieldTwoTwo: "e"
           }
        },
   }})
Асеферов
источник
2
Отлично, это ответы на часть а! вы знаете лучший метод для части b?
isaacsultan
1
меняется также то же самое, только что прошедшее, masterFieldвместо masterField2 setExampleState({...exampleState, masterField:{// новых значений}
aseferov
3
Помните о неглубоком копировании при использовании оператора распространения. См., Например: stackoverflow.com/questions/43638938/…
Жоао Маркос Грис
7
Если вы используете то же состояние с его Диспетчером, вам следует использовать функцию. setExampleState( exampleState => ({...exampleState, masterField2: {...etc} }) );
Майкл Харли,
41

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

После того, как вы глубоко клонируете состояние, React пересчитает и повторно отрендерит все, что зависит от переменных, даже если они не изменились!

Итак, прежде чем пытаться решить вашу проблему, подумайте, как вы можете сначала сгладить состояние. Как только вы это сделаете, вы найдете прекрасные инструменты, которые помогут справиться с большими состояниями, такие как useReducer ().

Если вы подумали об этом, но все же уверены, что вам нужно использовать глубоко вложенное дерево состояний, вы все равно можете использовать useState () с такими библиотеками, как immutable.js и Immutability-helper . Они упрощают обновление или клонирование глубоких объектов, не беспокоясь об изменчивости.

Ильяс Ассаинов
источник
Вы также можете использовать Hookstate (отказ от ответственности: я являюсь автором) для управления сложными (локальными и глобальными) данными состояния без глубокого клонирования и не беспокоясь о ненужных обновлениях - Hookstate сделает это за вас.
Эндрю
40

Если кто-то ищет обновление хуков useState () для объекта

- Through Input

        const [state, setState] = useState({ fName: "", lName: "" });
        const handleChange = e => {
            const { name, value } = e.target;
            setState(prevState => ({
                ...prevState,
                [name]: value
            }));
        };

        <input
            value={state.fName}
            type="text"
            onChange={handleChange}
            name="fName"
        />
        <input
            value={state.lName}
            type="text"
            onChange={handleChange}
            name="lName"
        />
   ***************************

 - Through onSubmit or button click
    
        setState(prevState => ({
            ...prevState,
            fName: 'your updated value here'
         }));
Махешвирус
источник
3
Именно то, что я искал. Потрясающие!
StackedActor
7

Спасибо, Филип, это помогло мне - в моем случае у меня была форма с множеством полей ввода, поэтому я сохранил исходное состояние как объект, и я не смог обновить состояние объекта. Приведенный выше пост помог мне :)

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} 
/>


Кавита Викас
источник
6

Я опаздываю на вечеринку .. :)

@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"

    }));

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

Оламигоке Филип
источник
У меня есть вопрос. Почему нам нужно заключать функцию в круглые скобки? Я не уверен, что происходит под капотом. Вы случайно не знаете, или где я могу прочитать об этом больше?
Xenon
1
@MichaelRamage Почему мы заключаем функцию в скобки ():: Чтобы просто ответить на этот вопрос; это потому, что setInfoData по своей природе он имеет высокий порядок, т.е. он может принимать другую функцию в качестве аргумента, благодаря мощности, предоставляемой Hook: useState. Я надеюсь, что эта статья внесет больше ясности в функции высшего порядка: sitepoint.com/higher-order-functions-javascript
Olamigoke Philip
1
Очень полезно, особенно для случая, когда вы пытаетесь обновить свойство, вложенное в несколько уровней, например: first.second.third - другие примеры охватывают first.second, но не first.second.third
Craig Presti - MSFT
3
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} />&nbsp; {items.title}</label>
          </div>
        )
      })}
    </div>
  );
}
Викас Кумар
источник
1

Думаю, лучшее решение - Immer . Это позволяет вам обновлять объект, как если бы вы напрямую изменяли поля (masterField.fieldOne.fieldx = 'abc'). Но реального объекта это, конечно, не изменит. Он собирает все обновления для чернового объекта и дает вам окончательный объект в конце, который вы можете использовать для замены исходного объекта.

Дениз
источник
0

Я оставляю вам служебную функцию для неизменного обновления объектов

/**
 * 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

Гонсало
источник
0

Изначально я использовал объект в useState, но затем перешел на хук useReducer для сложных случаев. Я почувствовал улучшение производительности при рефакторинге кода.

useReducer обычно предпочтительнее useState, когда у вас есть сложная логика состояния, которая включает в себя несколько подзначений, или когда следующее состояние зависит от предыдущего.

useReducer React документы

Я уже реализовал такой хук для себя:

/**
 * 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);
      }
    },
  ];
}

больше связанных крючков .

эялиоли
источник