Bereaksi: Melewati Prop di w/ acara untuk setState

Saya sedang bekerja dengan objek status bersarang yang telah saya perbarui dengan fungsi onChange, seperti:

  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>

Ini menyelesaikan triknya. Namun saat ini, jika saya ingin memperbarui beberapa properti negara melalui formulir besar dengan dropdown/bidang input dll, saya harus membuat kode keras 6 penangan onChange yang berbeda untuk bidang tersebut.

Sebaliknya, saya lebih memilih untuk hanya memiliki satu pengendali onChange, dan meneruskan status dari bidang formulir untuk properti status yang saya ubah, tetapi saya tidak dapat memahami sintaksisnya:


  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>

Saya telah mencoba menggunakan berbagai jenis sintaksis untuk merangkai nilai 'prop' yang diteruskan ke prevState:

prevState.prop = e.target.value;

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

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

Namun fungsinya tidak pernah mengenali "prop" yang saya berikan dari fungsi tersebut. Saya yakin pasti ada cara sederhana untuk melakukan ini. Bantuan apa pun akan sangat dihargai.


person snejame    schedule 07.09.2019    source sumber


Jawaban (3)


Apakah harus berupa satu kait useState? Saya akan merekomendasikan menggunakan useReducer atau menyederhanakannya sedikit dengan beberapa kait useState.

Beberapa useState kait

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);

Edit formulir-dengan-multiple-hooks

Tunggal useReducer (dengan keadaan disederhanakan)

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);

Edit formulir-dengan-reducer

person skovy    schedule 07.09.2019
comment
Terima kasih banyak atas tanggapan Anda. Namun pertanyaannya. . .apakah ada keuntungan menggunakan peredam dibandingkan dengan rekomendasi lodash dari @ckedar? Tampaknya itu adalah cara terbersih untuk menjangkau item yang disarangkan. - person snejame; 07.09.2019
comment
Sepertinya dia sudah menghapus komentarnya. Sudahlah. . . - person snejame; 07.09.2019
comment
Saya juga memperhatikan bahwa fungsi peredam dideklarasikan di luar komponen. Jika saya mengimpor komponen ini dengan banyak komponen serupa lainnya dengan reduksi, apakah ini akan menimbulkan masalah? - person snejame; 07.09.2019
comment
Salah satu keuntungannya adalah dengan peredam Anda memiliki lebih banyak fleksibilitas dan mengontrol keadaan dengan tepat. Anda juga dapat melakukan hal-hal unik per tindakan/bidang dan menambahkan tindakan baru (misalnya: hapus atau setel ulang). Dan itu tidak memerlukan penambahan ketergantungan lain (karena ini adalah vanilla React). - person skovy; 07.09.2019
comment
satu pertanyaan lagi di sini. Bagaimana jika saya perlu menjangkau objek array pelanggan kedua dalam keadaan asli yang tercantum di atas untuk menetapkan nama (state.customer[1].name)? Sepertinya saya tidak dapat menemukan sintaks yang tepat untuk itu. Saya mendapatkan yang berikut ini: ``` case SET_CUST1_NAME: return { ...state, customer: [ { ...state.customer[1], name: value } ] }; ``` - person snejame; 09.09.2019

useReducer adalah pilihan yang lebih baik untuk melakukan ini. Contohnya tersebar di internet.

Mengapa Anda tidak boleh menggunakan useState untuk meneruskan suatu objek adalah karena objek tersebut tidak bertindak seperti setState. Referensi objek yang mendasarinya sama. Oleh karena itu, react tidak akan pernah memicu perubahan keadaan. Jika Anda ingin menggunakan useState yang sama untuk objek. Anda mungkin harus menerapkan versi Anda sendiri untuk memperluasnya (contoh di bawah) atau Anda dapat langsung menggunakan kait useReducer untuk mencapai hal yang sama.

Berikut ini contoh dengan useState agar Anda dapat memperhatikan pembaruan status pada setiap perubahan.

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

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

Perhatikan ...form di sana. Anda dapat melakukannya di setiap pembaruan yang Anda inginkan atau Anda dapat menggunakan utilitas Anda sendiri atau useReducer seperti yang saya sebutkan.

Sekarang mengenai kode Anda, ada kekhawatiran lain.

  1. Anda menggunakan phone Anda sebagai array yang bisa menjadi objek. Atau lebih baik lagi, properti terpisah juga bisa digunakan. Tidak ada salahnya.

  2. Jika Anda memiliki customers sebagai array, Anda harus mengulang catatan. Tidak hanya memperbarui indeks dengan hardcoding. Jika hanya ada satu pelanggan sebaiknya tidak menyimpan array tetapi hanya sebuah objek. Dengan asumsi itu adalah array pelanggan, dan Anda mengulanginya, berikut cara memperbarui 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 

Sebaliknya, saya lebih suka hanya memiliki satu pengendali onChange, dan meneruskan status dari bidang formulir untuk properti status yang saya ubah, tetapi saya tidak tahu sintaksisnya

Jika Anda belum mengetahuinya dari contoh pertama. Berikut caranya dalam langkah singkat.

  1. Berikan atribut nama pada elemen HTML Anda.
  2. Kemudian gunakan [e.target.name]
   return { ...item, phone: { [e.target.name]: e.target.value }}; 

person HalfWebDev    schedule 07.09.2019
comment
Terima kasih banyak atas tanggapan Anda. Saya akan memeriksa useReducers. Sayangnya saya tidak dapat mengubah struktur objek negara yang saya kerjakan. . .jika tidak, saya pasti melihat manfaat dari restrukturisasi negara dan menggunakan metode yang Anda sarankan. - person snejame; 07.09.2019
comment
Saya tidak mengatakan Anda menggunakannya secara wajib. Saya menjelaskan kepada Anda alasan menggunakan apa dan apa yang BUKAN dalam hal useState dan menggunakan Reducer. Selain itu, saya menunjukkan cara Anda TIDAK menggunakan loop untuk memperbarui di tempat yang seharusnya. Lain halnya jika one time update. Terima kasih karena Anda setidaknya menghargai jawaban saya. - person HalfWebDev; 07.09.2019

Gunakan pembantu _.set lodash.

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 
      };
    });
  };

BTW, di updateSomeStatePhone Anda saat ini, Anda sedang memodifikasi objek prevState yang seharusnya tidak dapat diubah.

person ckedar    schedule 07.09.2019
comment
Apakah ada keuntungan menggunakan ini dibandingkan peredam? - person snejame; 07.09.2019
comment
@yummylumpkins Lebih sedikit kode boilerplate. Tidak diperlukan perubahan dalam kode pembaruan jika menambahkan properti baru ke pelanggan. Kita dapat menulis peredam menggunakan _.set untuk menghilangkan kode boilerplate, tetapi secara umum saya akan menggunakan peredam ketika menangani keadaan yang lebih kompleks dengan interaksi/saling ketergantungan antar komponen keadaan. - person ckedar; 09.09.2019