Реакция: передача опоры с событием для setState

Я работаю с вложенным объектом состояния, который обновляю с помощью функций onChange, например:

  const [someState, setSomeState] = useState({
    customer: [
      {
        name: "Bob",
        address: "1234 Main Street",
        email: "[email protected]",
        phone: [
          {
            mobile: "555-5555",
            home: "555-5555"
          }
        ]
      }
    ]
  });

  const updateSomeStatePhone = e => {
    e.persist();
    setSomeState(prevState => {
      prevState.customer[0].phone[0].mobile = e.target.value;
      return {
        ...prevState
      };
    });
  };

 <p>Update Mobile Number<p>
  <select
   value={someState.customer[0].phone[0].mobile}
   onChange={updateSomeStatePhone}
  >
   <option value="123-4567">"123-4567"</option>
  </select>

Это делает фокус. Однако в настоящее время, если я хочу обновить несколько свойств состояния с помощью большой формы с раскрывающимися списками / полями ввода и т. Д., Мне нужно жестко закодировать 6 различных обработчиков onChange для этих полей.

Вместо этого я бы предпочел иметь только один обработчик onChange и передавать состояние из поля формы для свойства состояния, которое я изменяю, но я не могу понять синтаксис:


  const updateSomeState = (e, prop) => {
    e.persist();
    setSomeState(prevState => {
      prevState.prop = e.target.value;
      return {
        ...prevState
      };
    });
  };

 <p>Update Mobile Number<p>
  <select
   value={someState.customer[0].phone[0].mobile}
   onChange={updateSomeState(e, prop)}
  >
   <option value="123-4567">"123-4567"</option>
  </select>

Я пробовал использовать разные типы синтаксиса, чтобы связать переданное значение prop с prevState:

prevState.prop = e.target.value;

prevState.(prop) = e.target.value;

${prevState} + '.' + ${prop} = e.target.value; // Dumb, I know

Но функция никогда не распознает «опору», которую я передаю от функции. Я уверен, что должен быть простой способ сделать это. Любая помощь будет принята с благодарностью.


person snejame    schedule 07.09.2019    source источник


Ответы (3)


Это должен быть единственный useState хук? Я бы рекомендовал использовать useReducer или немного упростить его с помощью нескольких useState ловушек.

Несколько useState крючков

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [name, setName] = React.useState("");
  const [address, setAddress] = React.useState("");
  const [email, setEmail] = React.useState("");
  const [mobile, setMobile] = React.useState("");
  const [home, setHome] = React.useState("");

  const getResult = () => ({
    customer: [
      {
        name,
        address,
        email,
        phone: [
          {
            mobile,
            home
          }
        ]
      }
    ]
  });

  // Do whatever you need to do with this
  console.log(getResult());

  return (
    <>
      <input
        value={name}
        placeholder="name"
        onChange={e => setName(e.target.value)}
      />
      <br />
      <input
        value={address}
        placeholder="address"
        onChange={e => setAddress(e.target.value)}
      />
      <br />
      <input
        value={email}
        placeholder="email"
        onChange={e => setEmail(e.target.value)}
      />
      <br />
      <input
        value={mobile}
        placeholder="mobile"
        onChange={e => setMobile(e.target.value)}
      />
      <br />
      <input
        value={home}
        placeholder="home"
        onChange={e => setHome(e.target.value)}
      />
    </>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

 Редактировать форму с несколькими крючками

Одиночный useReducer (с упрощенным состоянием)

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

const reducer = (state, action) => {
  const { type, value } = action;

  switch (type) {
    case "SET_NAME":
      return { ...state, name: value };
    case "SET_ADDRESS":
      return { ...state, address: value };
    case "SET_EMAIL":
      return { ...state, email: value };
    case "SET_MOBILE":
      return { ...state, phone: [{ ...state.phone[0], mobile: value }] };
    case "SET_HOME":
      return { ...state, phone: [{ ...state.phone[0], home: value }] };
    default:
      throw Error(`Unexpected action: ${action.type}`);
  }
};

const initialState = {
  name: "",
  address: "",
  email: "",
  phone: [
    {
      mobile: "",
      home: ""
    }
  ]
};

function App() {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  // Do what you need with state
  console.log(state);

  return (
    <>
      <input
        value={state.name}
        placeholder="name"
        onChange={({ target: { value } }) =>
          dispatch({ type: "SET_NAME", value })
        }
      />
      <br />
      <input
        value={state.address}
        placeholder="address"
        onChange={({ target: { value } }) =>
          dispatch({ type: "SET_ADDRESS", value })
        }
      />
      <br />
      <input
        value={state.email}
        placeholder="email"
        onChange={({ target: { value } }) =>
          dispatch({ type: "SET_EMAIL", value })
        }
      />
      <br />
      <input
        value={state.phone.mobile}
        placeholder="mobile"
        onChange={({ target: { value } }) =>
          dispatch({ type: "SET_MOBILE", value })
        }
      />
      <br />
      <input
        value={state.phone.home}
        placeholder="home"
        onChange={({ target: { value } }) =>
          dispatch({ type: "SET_HOME", value })
        }
      />
    </>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

 Редактировать форму с редуктором

person skovy    schedule 07.09.2019
comment
Спасибо большое за Ваш ответ. Хотя вопрос. . .Есть ли преимущество использования редуктора по сравнению с рекомендацией lodash @ckedar? Кажется, это самый чистый способ добраться до вложенных элементов. - person snejame; 07.09.2019
comment
Ну вроде он удалил свой комментарий. Неважно. . . - person snejame; 07.09.2019
comment
Я также заметил, что функция редуктора объявлена ​​вне компонента. Если я импортирую этот компонент со многими другими подобными компонентами с редукторами, возникнет ли это проблема? - person snejame; 07.09.2019
comment
Одним из преимуществ является то, что с редуктором у вас больше гибкости и точного контроля состояния. Вы также можете делать уникальные вещи для каждого действия / поля и добавлять новые действия (например, очистить или сбросить). И это не требует добавления другой зависимости (поскольку это ванильный React). - person skovy; 07.09.2019
comment
еще один вопрос здесь. Что, если мне нужно связаться со вторым объектом массива клиентов в исходном состоянии, указанном выше, чтобы задать имя (state.customer [1] .name)? Кажется, я не могу найти для этого правильный синтаксис. У меня есть следующее: `` case SET_CUST1_NAME: return {... state, customer: [{... state.customer [1], name: value}]}; `` '' - person snejame; 09.09.2019

useReducer - лучший выбор для этого. Примеры по всему Интернету.

Почему вы не должны использовать useState для передачи объекта, потому что он не действует как setState. Ссылка на базовый объект такая же. Следовательно, реакция никогда не вызовет изменения состояния. Если вы хотите использовать тот же useState для объектов. Возможно, вам придется реализовать свою собственную версию, чтобы расширить это (пример ниже), или вы можете напрямую использовать useReducer, чтобы добиться того же.

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

const [form, setValues] = useState({
    username: "",
    password: ""
});

const updateField = e => {
    setValues({
      ...form,
      [e.target.name]: e.target.value
    });
};

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

Теперь, переходя к вашему коду, есть и другие проблемы.

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

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

const updatedCustomers =  state.customers.map(item => {

   const { phone } = item;
   return { ...item, phone: { mobile: e.target.value }}; 
   // returns newCustomer object with updated mobile property
});

// Then go ahead and call `setSomeState ` from `useState`
setSomeState(...someState, { customer: updatedCustomers });// newState in your case is 

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

Если вы не поняли это из первого примера. Вот как это сделать короткими шагами.

  1. Дайте вашему элементу HTML атрибут name.
  2. Тогда вместо этого используйте [e.target.name]
   return { ...item, phone: { [e.target.name]: e.target.value }}; 

person HalfWebDev    schedule 07.09.2019
comment
Большое спасибо за Ваш ответ. Посмотрю в useReducers. К сожалению, я не могу изменить структуру объекта состояния, с которым работаю. . В противном случае я определенно вижу ценность в реструктуризации состояния и использовании предложенных вами методов. - person snejame; 07.09.2019
comment
Я не говорил, что вы используете его в обязательном порядке. Я объяснил вам причину использования того, что и что НЕ, с точки зрения useState и использования Reducer. Кроме того, я указал, что вы НЕ используете цикл для обновления там, где вам следует. Другой случай, если это разовое обновление. Спасибо, что по крайней мере оценили мой ответ. - person HalfWebDev; 07.09.2019

Воспользуйтесь помощником lodash _.set.

const updateSomeState = (e, prop) => {
    e.persist();
    setSomeState(prevState => {
      let customers = [...prevState.customers] // make a copy of array
      let customer = {...customers[0]} // make a copy of customer object
      _.set(customer, prop, e.target.value)
      customers[0] = customer;
      return {
        ...prevState, customers 
      };
    });
  };

Кстати, в вашем существующем updateSomeStatePhone вы изменяете объект prevState, который должен быть неизменным.

person ckedar    schedule 07.09.2019
comment
Есть ли преимущества в использовании этого перед редуктором? - person snejame; 07.09.2019
comment
@yummylumpkins Меньше шаблонного кода. При добавлении нового свойства клиенту не требуется никаких изменений в коде обновления. Мы можем написать редуктор, используя _.set, чтобы исключить шаблонный код, но в целом я буду использовать редуктор при работе с более сложным состоянием с взаимодействием / взаимозависимостью между компонентами состояния. - person ckedar; 09.09.2019