React: ส่ง Prop ด้วยเหตุการณ์สำหรับ 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>

นี่จะทำให้เคล็ดลับเสร็จสิ้น อย่างไรก็ตาม ในปัจจุบัน หากฉันต้องการอัปเดตคุณสมบัติสถานะหลายรายการผ่านแบบฟอร์มขนาดใหญ่ที่มีฟิลด์ดรอปดาวน์/อินพุต ฯลฯ ฉันต้องฮาร์ดโค้ดตัวจัดการ onChange ที่แตกต่างกัน 6 ตัวสำหรับฟิลด์เหล่านั้น

แต่ฉันอยากให้มีตัวจัดการ 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

แต่ฟังก์ชันไม่เคยรู้จัก "prop" ที่ฉันส่งผ่านจากฟังก์ชัน ฉันแน่ใจว่าต้องมีวิธีง่ายๆ ในการทำเช่นนี้ ความช่วยเหลือใด ๆ ที่จะได้รับการชื่นชมอย่างมาก.


person snejame    schedule 07.09.2019    source แหล่งที่มา


คำตอบ (3)


จำเป็นต้องuseStateฮุกตัวเดียวมั้ย? ฉันอยากจะแนะนำให้ใช้ useReducer หรือทำให้ง่ายขึ้นเล็กน้อยด้วย useState hooks หลายอัน

ตะขอ 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
ข้อดีประการหนึ่งคือด้วยตัวลด คุณจะมีความยืดหยุ่นมากขึ้นและควบคุมสถานะได้อย่างแม่นยำ คุณยังสามารถทำสิ่งที่ไม่ซ้ำกันต่อการกระทำ/ฟิลด์ และเพิ่มการกระทำใหม่ (เช่น ล้างหรือรีเซ็ต) และไม่จำเป็นต้องเพิ่มการพึ่งพาอื่น (เนื่องจากเป็น vanilla 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 hook เพื่อให้บรรลุผลเช่นเดียวกัน

นี่คือตัวอย่างที่มี 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 ของคุณ
  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
ฉันไม่ได้บอกว่าคุณใช้มันโดยบังคับ ฉันอธิบายเหตุผลในการใช้ what และ what NOT ในแง่ของ useState และใช้ตัวลด นอกจากนี้ ฉันยังได้ชี้ให้เห็นว่าคุณไม่ได้ใช้ลูปเพื่ออัปเดตในส่วนที่คุณควรทราบ อีกกรณีหนึ่งหากเป็นการอัพเดตครั้งเดียว ขอบคุณที่คุณรู้สึกอยากชื่นชมคำตอบของฉันอย่างน้อย - person HalfWebDev; 07.09.2019

ใช้ตัวช่วย _.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 ใน updateSomeStatePhone ที่มีอยู่ของคุณ คุณกำลังแก้ไข prevState วัตถุซึ่งควรจะไม่เปลี่ยนรูป

person ckedar    schedule 07.09.2019
comment
มีข้อได้เปรียบในการใช้สิ่งนี้กับตัวลดขนาดหรือไม่? - person snejame; 07.09.2019
comment
@yummylumpkins รหัสสำเร็จรูปน้อยลง ไม่จำเป็นต้องเปลี่ยนแปลงรหัสอัปเดตหากเพิ่มคุณสมบัติใหม่ให้กับลูกค้า เราสามารถเขียนตัวลดขนาดโดยใช้ _.set เพื่อกำจัดโค้ดสำเร็จรูป แต่โดยทั่วไปแล้ว ฉันจะใช้ตัวลดขนาดเมื่อต้องจัดการกับสถานะที่ซับซ้อนมากขึ้นด้วยการโต้ตอบ/การพึ่งพาระหว่างส่วนประกอบของสถานะ - person ckedar; 09.09.2019