2

I am trying to create object based on user input. I did following till now.

It gives me following error:

Uncaught TypeError: Cannot read property 'fullname' of undefined

const App = () => {

  let passengerObj = {
        "primary": {
            "fullname": "",
            "age": null,
        },
        "secondary": [],
    }
    
    const [passengerdata, setPassengerData] = useState(passengerObj)

        return (
            <form className="passenger-form">
                    <input 
                        type="text" 
                        placeholder="Full Name" 
                        value={passengerdata.primary.fullname}
                        onChange={setPassengerData}
                        required/>
                    <input 
                        type="number" 
                        placeholder="Age" 
                        value={passengerdata.primary.age || ""}
                        onChange={setPassengerData}
                        required/>
                  <div className="secondary-passenger-data">
                        <input 
                            type="text" 
                            placeholder="Full Name of Passenger2"
                            required/>
                        <input type="number"
                            placeholder="Age Passenger2"
                            required/>
                   </div>
                   <div className="secondary-passenger-data">
                        <input 
                            type="text" 
                            placeholder="Full Name of Passenger3"
                            required/>
                        <input type="number"
                            placeholder="Age Passenger3"
                            required/>
                   </div>
             </form>
  )}

As I am trying to add primary and secondary data in passengerObj. So, I can save it. But, It want allow me to do that.

Not sure how can I add primary and secondary data to Object using hooks.

Here sandbox I reproduce it.

https://codesandbox.io/s/distracted-sunset-89ecj?file=/src/App.js:0-1184

Any would be greatly appreciated.

3
  • 2
    What did you expect on calling setPassengerData directly from onChange? I'd suggest basic debugging - console.log(passengerData). Commented Jun 14, 2020 at 12:33
  • @jonrsharpe I highlighted my expectation in bold. Commented Jun 14, 2020 at 12:38
  • But you aren't trying to do that. You're replacing the whole state with the default argument to onChange. Again, do some basic debugging, you'll quickly see why passengerData.primary becomes undefined. This isn't really related to React or hooks, the same thing would happen in vanilla JS with a regular assignment. Commented Jun 14, 2020 at 12:39

4 Answers 4

4

Write a common onChangeHandler which is dynamic enough to update multiple input elements.

Provide a name to your inputs. Supply a type(primary/secondary) & event to the handler.

Updated working demo

Handler

const handleChange = (e, type) => {
    const { target } = e;
    setPassengerData(prev => ({
      ...prev,
      [type]: {
        ...prev[type],
        [target.name]: target.value
      }
    }));
  };

JSX

<input
        type="text"
        placeholder="Full Name"
        value={passengerdata.primary.fullname}
        onChange={e => handleChange(e, "primary")}
        name="fullname"
        required
      />
Sign up to request clarification or add additional context in comments.

Comments

1

What is actually happening in your code when you are entering some data for fullname or age the entire state object is being overwritten by the event object. So the object will not have any property called primary so eventually when you are trying to access fullname its giving error.

Please update your code with the below code. I have added two methods in order to update the fullname and age in the state.

let passengerObj = {
  "primary": {
      "fullname": "",
      "age": null,
  },
  "secondary": [],
}

const [passengerdata, setPassengerData] = useState(passengerObj)

const setAge = (age) => {
  setPassengerData({
    ...passengerdata,
    primary: {
      ...passengerdata.primary,
      "age": age
    }
  })
}

const setFullname = (name) => {
  setPassengerData({
    ...passengerdata,
    primary: {
      ...passengerdata.primary,
      "fullname": name
    }
  })
}
  return (
      <form className="passenger-form">
              <input 
                  type="text" 
                  placeholder="Full Name" 
                  value={passengerdata.primary.fullname}
                  onChange={(e) => setFullname(e.target.value)}
                  required/>
              <input 
                  type="number" 
                  placeholder="Age" 
                  value={passengerdata.primary.age || ""}
                  onChange={(e) => setAge(e.target.value)}
                  required/>
            <div className="secondary-passenger-data">
                  <input 
                      type="text" 
                      placeholder="Full Name of Passenger2"
                      required/>
                  <input type="number"
                      placeholder="Age Passenger2"
                      required/>
             </div>
             <div className="secondary-passenger-data">
                  <input 
                      type="text" 
                      placeholder="Full Name of Passenger3"
                      required/>
                  <input type="number"
                      placeholder="Age Passenger3"
                      required/>
             </div>
       </form>
)}

Here is another version which is generic method in order to update any input field.

let passengerObj = {
  "primary": {
      "fullname": "",
      "age": null,
  },
  "secondary": [],
}

const [passengerdata, setPassengerData] = useState(passengerObj);

const setFormData = (name, value) => {
  setPassengerData(passengerData => {
    ...passengerData,
    primary: {
      ...passengerData.primary,
      [name]: value,
    }
  })
}

return (
      <form className="passenger-form">
              <input 
                  name="fullname"
                  type="text" 
                  placeholder="Full Name" 
                  value={passengerdata.primary.fullname}
                  onChange={(e) => setFormData(e.target.name, e.target.value)}
                  required/>
              <input 
                  name="age"
                  type="number" 
                  placeholder="Age" 
                  value={passengerdata.primary.age || ""}
                  onChange={(e) => setFormData(e.target.name, e.target.value)}
                  required/>
            <div className="secondary-passenger-data">
                  <input 
                      type="text" 
                      placeholder="Full Name of Passenger2"
                      required/>
                  <input type="number"
                      placeholder="Age Passenger2"
                      required/>
             </div>
             <div className="secondary-passenger-data">
                  <input 
                      type="text" 
                      placeholder="Full Name of Passenger3"
                      required/>
                  <input type="number"
                      placeholder="Age Passenger3"
                      required/>
             </div>
       </form>
)

Hope this helps.

5 Comments

There is closure on the e.target.value, check your answer, try typing something
This works perfectly fine, I don't see any issue. I have tried the same code in the sandbox provided in the question and is working as expected.
You are right, my bad, I thought it was setPassengerData(prev => ...) functional update, but still, this answer will recreate all functions on every render, I upvote as this is a valid answer
I agree on the point that all the functions will get recreated on every render, but then unless it's causing so much of performance issues using useCallback is also having its own drawbacks so haven't used that version.
Just few drawbacks, it will recreate all functions in every render (not so important), you won't be able to reuse such functions (like setAge) because of closures on passengerdata, meaning you can't pass it as a callback somewhere. But sure, for changing an input its a valid answer
0

This maybe hepls

enter image description here

function App() {
  let passengerObj = {
    "primary": {
      "fullname": "",
      "age": null,
    },
    "secondary": [],
  };

  const [passengerdata, setPassengerData] = React.useState(passengerObj);

  return (
    <form className="passenger-form">
      <input
        type="text"
        placeholder="Full Name"
        value={passengerdata.primary.fullname}
        onChange={(e) => {
          const {value} = e.target;
          setPassengerData((passengerdata) => ({
            ...passengerdata,
            primary: {
              ...passengerdata.primary,
              fullname: value
            }
          }));
        }}
        required/>
      <input
        type="number"
        placeholder="Age"
        value={passengerdata.primary.age || ""}
        onChange={() => {

        }}
        required/>
      <div className="secondary-passenger-data">
        <input
          type="text"
          placeholder="Full Name of Passenger2"
          required/>
        <input type="number"
               placeholder="Age Passenger2"
               required/>
      </div>
      <div className="secondary-passenger-data">
        <input
          type="text"
          placeholder="Full Name of Passenger3"
          required/>
        <input type="number"
               placeholder="Age Passenger3"
               required/>
      </div>
    </form>
  );
}

note const {value} = e.target;

Comments

0

The problem is with onChange implementation, it accepts an event object and you trying to set the event as passengerData.

But there is another problem when using setState, you want to use functional updates to not get a stale state.

But, using e.target.event in a callback will have value closure on this value, so you should use a reference with useRef hook:

This solution won't recreate onChange on every render (because of useCallback and functional update of useState), and also won't have any stale state.

const passengerObj = {
  primary: {
    fullname: '',
    age: null
  },
  secondary: []
};

const App = () => {
  const [passengerdata, setPassengerData] = useState(passengerObj);

  const inputRef = useRef();

  const onChange = useCallback(() => {
    setPassengerData(prev => ({
      ...prev,
      primary: {
        ...prev.primary,
        fullname: inputRef.current.value
      }
    }));
  }, []);

  return (
    <input
      ref={inputRef}
      value={passengerdata.primary.fullname}
      onChange={onChange}
    />
  );
};

Edit beautiful-sammet-mir70

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.