React: Passing a Prop in w/ event for setState

半世苍凉 提交于 2020-01-16 00:45:34

问题


I'm working with a nested state object that I have been updating with onChange functions, like so:

  const [someState, setSomeState] = useState({
    customer: [
      {
        name: "Bob",
        address: "1234 Main Street",
        email: "bob@mail.com",
        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>

This gets the trick done. Currently however, if I want to update multiple state properties via a large form with dropdowns/input fields etc, I have to hard code 6 different onChange handlers for those fields.

Instead, I would prefer to have only one onChange handler, and pass in the state from the form field for the state property that I am changing, but I can't figure out the syntax:


  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>

I've tried using different types of syntax to chain the passed in 'prop' value to prevState:

prevState.prop = e.target.value;

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

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

But the function never recognizes the "prop" that I pass in from the function. I'm sure there must be a simple way to do this. Any help would be greatly appreciated.


回答1:


Does it have to be a single useState hook? I would recommend using useReducer or simplifying it a bit with multiple useState hooks.

Multiple useState hooks

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

Single useReducer (with simplified state)

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




回答2:


useReducer is a better choice for doing this. Examples all over the internet.

Why you shouldn't use useState to pass an object is because it doesn't act like setState. The underlying object reference is the same. Therefore, react will never trigger a state change. In case you want to use the same useState for objects. You may have to implement your own version to extend that (example below ) or you can directly use useReducer hook to achieve the same.

Here's an example with useState for you to notice the state update on every change.

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

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

Notice the ...form in there. You can do it this in every update you want or you can use your own utility or useReducer as I mentioned.

Now coming to your code, there are other concerns.

  1. You are using your phone as an array which can be an object. Or better yet separate properties will do as well. No harm.

  2. If you have customers as an array, you have to loop through the records. Not just update the index by hardcoding. If there's only one customer better not keep the array but just an object. Assuming it is an array of customers, and you are looping through it, here's how to update 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 

Instead, I would prefer to have only one onChange handler, and pass in the state from the form field for the state property that I am changing, but I can't figure out the syntax

If you haven't figured that out from the first example. Here's how in short steps.

  1. Give your HTML element a name attribute.
  2. Then instead use the [e.target.name]
   return { ...item, phone: { [e.target.name]: e.target.value }}; 




回答3:


Use lodash's _.set helper.

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, in your existing updateSomeStatePhone you are modifying prevState object which is supposed to be immutable.



来源:https://stackoverflow.com/questions/57830805/react-passing-a-prop-in-w-event-for-setstate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!