System throws exception and display playerProfile.map is not a function - reactjs

I would like to display data received in the Player Profile fields. Started with name field, it displays name data, but while trying to edit the name text field, system throws following exception, TypeError: playerProfile.map is not a function. I have wrapped the fetch call inside the arrow function. Can someone please advise on what is the root cause of this error.
Note: At the moment I have received the value for name field only, need to display for other fields and still need to work on handleSubmit()
Detailed error message from console:
Uncaught TypeError: playerProfile.map is not a function
at Profile (Profile.js:34)
at renderWithHooks (react-dom.development.js:14803)
at updateFunctionComponent (react-dom.development.js:17034)
at beginWork (react-dom.development.js:18610)
at HTMLUnknownElement.callCallback (react-dom.development.js:188)
at Object.invokeGuardedCallbackDev (react-dom.development.js:237)
at invokeGuardedCallback (react-dom.development.js:292)
at beginWork$1 (react-dom.development.js:23203)
at performUnitOfWork (react-dom.development.js:22157)
My Sample Code
const [playerProfile, setPlayerProfile] = useState([]);
const handleSubmit = (e) => {
e.preventDefault()
}
const onChange = (e) => {
e.persist();
setPlayerProfile({ ...playerProfile, [e.target.name]: e.target.value });
}
useEffect(() => {
const fetchData = async () => {
try {
const res = await Axios.get('http://localhost:8000/service/profile')
setPlayerProfile(res.data.playerProfile);
} catch (e) {
console.log(e);
}
}
fetchData();
}, []);
return (
<div className="register_player_Twocolumn_layout_two">
<form onSubmit={handleSubmit} className="myForm">
{
playerProfile.map(({ id, image, name, email, position, privilege, password }) =>(
<div>
<div key={id} className="formInstructionsDiv formElement">
<h2 className="formTitle">Player Profile</h2>
<div className="register_profile_image">
<input id="profilePic" name="photo" type="file"/>
</div>
<div className="previewProfilePic" >
<img alt="" error="" name="previewImage" className="playerProfilePic_home_tile" src=""></img>
</div>
</div>
<div className="fillContentDiv formElement">
<label>
<input className="inputRequest formContentElement" name="name" type="text" key={name} value={name} onChange={onChange}/>
</label>
<label>
<input className="inputRequest formContentElement" name="email" type="text"/>
</label>
<label>
<div className="select" >
<select name="privilege" id="select">
<option value="player">PLAYER</option>
<option value="admin">ADMIN</option>
</select>
</div>
</label>
<label>
<input className="inputRequest formContentElement" name="password" type="password"/>
</label>
</div>
<div className="submitButtonDiv formElement">
<button type="submit" className="submitButton">Save</button>
</div>
</div>
))
}
</form>
</div>
);

TLDR: Check the Sandbox
#soccerway, based on our comments pointed out as per typos in you approach, here is some code that attempts to fix them. The link to the Live Codesandbox
SOME Context
When you define you playerProfile component state, you initialize it as an array, successfully update it from the server as an array but mess it up in the input onChange handler. Let's say you type s in the name input. With this...
setPlayerProfile({ ...playerProfile, [e.target.name]: e.target.value });
...you are transforming the playerProfile form this array.
// Fetched playerProfile from the api.
playerProfile = [
{
name: "David",
email: "david#testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\city.JPG",
position: "FORWARD",
updatedAt: "2020-05-25T11:02:16.000Z"
},
// Extra profile put to have a solid example
{
name: "Goriath",
email: "goriath#testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\goriath.JPG",
position: "MIDI",
updatedAt: "2020-05-26T11:02:16.000Z"
},
]
// To This Object
playerProfile = {
0: {
name: "David",
email: "david#testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\city.JPG",
position: "FORWARD",
updatedAt: "2020-05-25T11:02:16.000Z"
},
1: {
name: "Goriath",
email: "goriath#testmail.com",
phonenumber: null,
id: 6,
privilege: "PLAYER",
photo: "C:\\fakepath\\goriath.JPG",
position: "MIDI",
updatedAt: "2020-05-26T11:02:16.000Z"
},
name: Davids"
}
Like you see, you cannot map over an object unless if you get its keys or entries, in which case the approach would still be invalid by the second element in the object.
The other issue is that you are trying to update an object, and directly appending it to the array/object. If the update is successful, this will result into duplicate data save for the name. You need to find the old object in state and update it, then totally replace it. That would be fine if your data was normalized, like saved by keys initially. Something like this...
data= {
playerProfilesById = {
5: { // Player ID is the key
name: "David",
email: "david#testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\city.JPG",
position: "FORWARD",
updatedAt: "2020-05-25T11:02:16.000Z"
},
6: {
name: "Goriath",
email: "goriath#testmail.com",
phonenumber: null,
id: 6,
privilege: "PLAYER",
photo: "C:\\fakepath\\goriath.JPG",
position: "MIDI",
updatedAt: "2020-05-26T11:02:16.000Z"
},
},
playerProfileIds=[5,6]
}
That way its easy to update playerProfilesById with your approach, with the [e.target.id](assuming you're passing the input tag it's id) not [e.target.name], while using the playerProfileIds to map over the items in the jsx.
However if you don't have control over the api data format, you could instead make sure you update your handler to receive an id(assuming the id is unique) from the onChange, use that id to find the profile in the array.
While finding, you could keep the element array Index, and use it to directly target and update the array. (Commented out approach in handler)
Or you could just map over the entire array and update the profile that changed, then use that data to eventually update state.
Below is the full approach.
import React, { useState, useEffect } from "react";
// import axios from "axios";
/* Assuming your api returns data in the follwoing format... */
const fakeAPICall = () => {
// CALL TO AXIO MUTED
// const res = await axios.get("http://localhost:8000/service/profile");
// NOTE: Please normalize this data so it's easy to update
// READ ABOUT: https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape
const data = {
playerProfile: [
{
name: "David",
email: "david#testmail.com",
phonenumber: null,
id: 5,
privilege: "PLAYER",
photo: "C:\\fakepath\\city.JPG",
position: "FORWARD",
updatedAt: "2020-05-25T11:02:16.000Z"
},
{
name: "Goriath",
email: "goriath#testmail.com",
phonenumber: "1234345234",
id: 6,
privilege: "PLAYER",
photo: "C:\\fakepath\\goriath.JPG",
position: "MIDFIELDER",
updatedAt: "2020-05-26T11:02:16.000Z"
}
]
};
return { data };
};
const PlayerProfile = () => {
// Note that your player profile is defined as an array in state.
// Remember to always keep it that way when updating it.
const [playerProfile, setPlayerProfile] = useState([]);
const handleSubmit = e => {
e.preventDefault();
};
// Pass the id to the handler so you will know which item id changing.
const handleChange = (e, id) => {
e.persist();
let itemIndex;
const targetPlayer = playerProfile.find((player, index) => {
console.log({ player, id, index });
itemIndex = index; // Track the index so you can use it to update later.
return player.id === id;
});
console.log({ targetPlayer, id, e });
const editedTarget = {
...targetPlayer,
[e.target.name]: e.target.value
};
const tempPlayers = Array.from(playerProfile);
tempPlayers[itemIndex] = editedTarget;
/*
// Alternatively:: you can just map over the array if you dont want to track the index
const tempPlayers = playerProfile.map((profile, index) => {
return profile.id === id ? editedTarget : profile;
});
*/
setPlayerProfile(tempPlayers);
};
useEffect(() => {
const fetchData = async () => {
try {
// const res = await axios.get("http://localhost:3000/api/products");
const res = await fakeAPICall();
console.log({ response: res });
setPlayerProfile(res.data.playerProfile);
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
console.log({ "⚽: playerProfile": playerProfile });
return (
<div className="register_player_Twocolumn_layout_two">
<h1>CAPTURE PLAYER PROFILE</h1>
<p>Form to capture player Profile</p>
<hr />
<form onSubmit={handleSubmit} className="myForm">
{playerProfile.map(
({ id, image, name, email, position, privilege, password }) => (
<div key={id}>
{/*2. Also put the key on the outer div in the map above */}
<div className="formInstructionsDiv formElement">
<h2 className="formTitle">Player Profile</h2>
<div className="register_profile_image">
<input id="profilePic" name="photo" type="file" />
</div>
<div className="previewProfilePic">
<img
alt=""
error=""
name="previewImage"
className="playerProfilePic_home_tile"
src=""
/>
</div>
</div>
<div className="fillContentDiv formElement">
<label>
NAME
<input
className="inputRequest formContentElement"
name="name"
type="text"
// key={name} // Remove this key or renmae it to id. Since name changes on rerender, it confuses react that the key is different and forces the element to toose focus
value={name}
onChange={e => handleChange(e, id)} // Pass the ID form here.
/>
</label>
<label>
<input
className="inputRequest formContentElement"
name="email"
type="text"
/>
</label>
<label>
<div className="select">
<select name="privilege" id="select">
<option value="player">PLAYER</option>
<option value="admin">ADMIN</option>
</select>
</div>
</label>
<label>
<input
className="inputRequest formContentElement"
name="password"
type="password"
/>
</label>
</div>
<div className="submitButtonDiv formElement">
<button type="submit" className="submitButton">
Save
</button>
</div>
</div>
)
)}
</form>
</div>
);
};
export default function App() {
return (
<div className="App">
<PlayerProfile />
</div>
);
}
PS: When you are mapping over items, each direct wrapper expects a unique key prop, that way react can know which component exactly changed to a avoid re-renders. In your approach, you're assigning the key to the input deep in the tree. Move it up to the outer most div wrapper.
Also make sure that whatever item you use as the key is unique, otherwise the items will keep loosing focus on updates if the key changes. For example in your code, the name is being changed but you are using it as the input. This results into a new key meaning you are working on a new element, eventually loosing focus on that input.

This might be one of your problem
<label>
<input className="inputRequest formContentElement"
name="name" type="text" key={name} value={name}
onChange ={onChange}/>
</label>
The value "name" of the name property of the input tag is not in the array of playerProfile.
I think it should be:
<label>
<input className="inputRequest formContentElement"
name={name} type="text" key={name} value={name}
onChange ={onChange}/>
</label>
that means your problem should be here
setPlayerProfile({ ...playerProfile, [e.target.name]: e.target.value });
playerProfile was an array, but the above line set's it to an object that's y it's throwing the error
This might work:
setPlayerProfile([ ...playerProfile, [e.target.name]: e.target.value ]);

Related

Pushing form values to an array. (React)

I'm having some trouble pushing the values from my form to an array that I'm mapping on screen.
const ForumTopic = [
{
title: "First Post",
messages: "test",
author: "Dagger",
count: 1,
date: "02/16",
},
];
const [topic, setTopic] = useState(ForumTopic);
Storing ForumTopic in state so I can add entries and display on screen after I click the submit button below.
const addTopic = (e) => {
e.preventDefault();
setTopic([...topic, e.target.value]);
};
<form onSubmit={addTopic}>
Create a topic title
<label htmlFor="title">
<input id="title"></input>
</label>
Write your message
<label htmlFor="message">
<textarea id="message"></textarea>
</label>
<label htmlFor="author">
<input id="author" defaultValue="Dagger" hidden></input>
</label>
<label htmlFor="count">
<input id="count" defaultValue="1" hidden></input>
</label>
<label htmlFor="date">
<input id="date" defaultValue="02/16/2023" hidden></input>
</label>
<button type="submit">
Post New Message
</button>
</form>
That's my code and form. The code is meant to push the values from each label in the form to create a new object inside the topic array. I want everything stored in a new object with the id of each label to match the names of each object (title, author, date, etc) but for some reason all I'm getting are undefined errors.
A simple way to do it is like this.
You need to obtain the value you are getting with an onChange in the input.
LINK to the example: https://stackblitz.com/edit/react-8r9f8l?file=src%2FApp.js
import React, { useState } from 'react';
const ForumTopic = [
{
title: 'First Post',
messages: 'test',
author: 'Dagger',
count: 1,
date: '02/16',
},
];
export default function App() {
const [topic, setTopic] = useState(ForumTopic);
const [inputObj, setInputObj] = useState({
title: '',
messages: '',
author: 'Dagger',
count: 1,
date: '02/16',
});
const handleChange = (event) => {
setInputObj((curr) => ({
...curr,
[event.target.name]: event.target.value,
}));
};
const addTopic = (e) => {
e.preventDefault();
setTopic([...topic, inputObj]);
};
return (
<>
<form onSubmit={addTopic}>
<label htmlFor="title">
Create a topic title
<input
id="title"
name="title"
value={inputObj.title}
onChange={handleChange}
></input>
</label>
<label htmlFor="message">
Write your message
<textarea
id="message"
name="messages"
value={inputObj.messages}
onChange={handleChange}
></textarea>
</label>
<label htmlFor="author">
<input id="author" name="author" defaultValue="Dagger" hidden></input>
</label>
<label htmlFor="count">
<input id="count" name="count" defaultValue="1" hidden></input>
</label>
<label htmlFor="date">
<input id="date" name="date" defaultValue="02/16/2023" hidden></input>
</label>
<button type="submit">Post New Message</button>
</form>
{topic.map((item) => {
return (
<>
<p>{item.title}</p>
<p>{item.messages}</p>
<p>{item.author}</p>
<p>{item.count}</p>
<p>{item.date}</p>
<span>------------</span>
</>
);
})}
</>
);
}
The problem is that on your addTopic function:
e.target.value are always undefined
to access the data you have to do:
const addTopic = (e) => {
e.preventDefault()
const myData = {
title: e.target.title.value,
message: e.target.message.value
}
setTopic(prev => [...prev, myData])
}

Unable to Edit Multi Input fields with React useState

I am new to react, i have an data object displaying it as form input default values.I need to update the object with input entry values. However i am able to edit only the first input field, but not other input fields. Could anyone help me what i am missing here.
Thank You
import { useState } from "react";
let userData = {
name: "Sample",
location: {
city: "Hyd",
population: "10000"
}
};
export default function App() {
const [data, setData] = useState(userData);
const handleChange = (event) => {
const name = event.target.name;
const value = event.target.value;
setData(previousState => {
return { ...previousState, [name]: value }
})
};
const handleSubmit = (e)=>{
e.preventDefault();
console.log(data);
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<form>
<div>
<label>
Enter Your Name:
<input
type="text"
name="name"
value={data.name || ""}
onChange={handleChange}
/>
</label>
</div>
<div>
<label>
Enter Your City:
<input
type="text"
name="city"
value={data.location.city || ""}
onChange={handleChange}
/>
</label>
</div>
<div>
<label>
Enter Population:
<input
type="text"
name="population"
value={data.location.population || ""}
onChange={handleChange}
/>
</label>
</div>
<div>
<input type="submit" value="Submit" onClick={handleSubmit}/>
</div>
</form>
</div>
);
}
the problem is that you’re saving the city and population inside location object and when you set the state your function doesn’t recognize the location object.
one solution is that you have a flat state object like this
let userData = {
name: "Sample",
city: "Hyd",
population: "10000"
};
another solution is to check if the name is city or population pass the new value inside location object

Is there any way of creating a component with images based on user input?

I am trying to let the user input a image that i later can display in another component, It is supposed to be displayed inside my "Card" component which is displayed inside my "Grid" component. The grid component then maps and displays all the houses in the "houses" array which also is a separate component. Manually creating houses inside the array works perfectly but when the user is trying to add a house everything but the images works. The console log in submitListing will show that the houses beeing added does contain an image but it is not beeing displayed in the grid. (Also, the first house that is submitted only contains an empty array for mainImg but the rest contains the "blob" image) Entire project: https://codesandbox.io/s/relaxed-orla-fqcwoy?file=/src/components/header/AddListing.jsx
HTML
<textarea
value={listing.area}
name="area"
onChange={handleChange}
className="area"
></textarea>
<input
onChange={handleInputChange}
name="mainImg"
type={"file"}
></input>
JS
const [listing, setListing] = useState({
area: "",
mainImg: [],
});
function handleChange(e) {
const { name, value } = e.target;
setListing((previousListing) => {
return { ...previousListing, [name]: value };
});
}
const [file, setFile] = useState();
function handleInputChange(e) {
setFile(URL.createObjectURL(e.target.files[0]));
}
function submitListing(e) {
e.preventDefault();
setListing({
area: "",
mainImg: file,
});
houses.push(listing);
console.log(houses);
}
So, here are the issues. I might forget something but i forked codesandbox, so you will be able to compare.
In your houses.js and AddListing.jsx you had mainImage and mainImg. You need to have a strict structure and naming, they had to match.
const [file, setFile] was.. useless, you had to save blobs into the listing object itself in the mainImage, img2, etc.
About the structure again. You had your mainImg, img2, ... set to an empty array, []. That have no sense due to you dont use <input type="file" multiple>. Switched them to undefined / string.
You cannot really bind value to the input type=file in react. Just remove the value from inputs.
Be careful with useEffects and useState's setValues, without understanding how they work it is really easy to make a mistake.
Here is your updated and working AddListing.jsx
import React, { useState, useCallback, useEffect } from "react";
import "./AddListing.css";
import houses from "../../houses";
const getDefaultListing = () => {
return {
id: houses.length + 1,
area: "",
state: "",
shortDesc: "",
longDesc: "",
pricePerNight: "",
cleaningFee: "",
serviceFee: "",
mainImage: undefined,
img2: undefined,
img3: undefined,
img4: undefined,
img5: undefined
};
};
function AddListing() {
const [listing, setListing] = useState(() => getDefaultListing());
const handleChange = useCallback((e) => {
const { name, value } = e.target;
setListing((prev) => ({ ...prev, [name]: value }));
}, []);
const handleInputChange = useCallback((e) => {
const { name, files } = e.target;
const [singleImage] = files;
if (!singleImage) {
setListing((prev) => ({ ...prev, [name]: undefined }));
} else {
const blob = URL.createObjectURL(singleImage);
setListing((prev) => ({ ...prev, [name]: blob }));
}
}, []);
const submitListing = useCallback(
(e) => {
e.preventDefault();
houses.push(listing);
setListing(() => getDefaultListing());
console.log(houses);
},
[listing]
);
useEffect(() => {
console.log(listing);
}, [listing]);
return (
<div className="add-listing-div">
<form>
<p id="p1">State</p>
<p id="p2">Area</p>
<textarea
value={listing.state}
name="state"
onChange={handleChange}
className="state"
/>
<textarea
value={listing.area}
name="area"
onChange={handleChange}
className="area"
/>
<p id="p3">Short Description</p>
<textarea
value={listing.shortDesc}
name="shortDesc"
onChange={handleChange}
className="short-description"
/>
<p id="p4">Long Description</p>
<textarea
value={listing.longDesc}
name="longDesc"
onChange={handleChange}
className="long-description"
/>
<p id="p5">Price/Night</p>
<p id="p6">Cleaning Fee</p>
<p id="p7">Service Fee</p>
<textarea
value={listing.pricePerNight}
name="pricePerNight"
onChange={handleChange}
className="price-per-night"
/>
<textarea
value={listing.cleaningFee}
name="cleaningFee"
onChange={handleChange}
className="cleaning-fee"
/>
<textarea
value={listing.serviceFee}
name="serviceFee"
onChange={handleChange}
className="service-fee"
/>
<label>Main Image: </label>
<input onChange={handleInputChange} name="mainImage" type="file" />
<label>Second Image: </label>
<input onChange={handleInputChange} name="img2" type="file" />
<label>Third Image: </label>
<input onChange={handleInputChange} name="img3" type="file" />
<label>Fourth Image: </label>
<input onChange={handleInputChange} name="img4" type="file" />
<label>Fifth Image: </label>
<input onChange={handleInputChange} name="img5" type="file" />
<button onClick={submitListing}>Submit</button>
</form>
</div>
);
}
export default AddListing;

Am I supposed to have this message in my console while I'm trying to "see" my input fields?

can someone tell me why am I getting this message in my console while I'm trying to send my data to my backend but the only thing that I get when I fill the title (for instance: Hello) is :
:
SyntheticBaseEvent {_reactName: 'onChange', _targetInst: null, type: 'change', nativeEvent: InputEvent, target: input#title.p-1.border-2.mx-2.rounded-sm, …}
Plus, I'm not able to see the data from my Dropdown and my checkboxe I can see title and the date selected but not the checkbox and the Dropdown...
I was expected to get :
{title:"H", date:"", choice: "", comment:""}
...
{title:"Hello", date:"", choice: "", comment:""}
... ( after chosing the date and the choice):
{title:"Hello", date:"2022-05-27", choice: true (if checkboxe is selected) comment: "Good"}...
Account:
export default function Account() {
const [data, setData] = useState({
title: "",
date: "",
choice: "",
comment:""
});
const COMMENT = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
function onSubmit(e) {
e.preventDefault();
axios
.post("", {
title: data.title,
date: data.date,
choice: data.choice
comment:data.comment
})
.then((res) => {
console.log(res.data);
});
}
function handleSubmit(e) {
const newData = { ...data };
newData[e.target.id] = e.target.value;
setData(newData);
console.log(e);
}
function Checkbox({ value }) {
const [checked, setChecked] = useState(value);
return (
<label>
<input
type="checkbox"
checked={checked}
onChange={(e) => setChecked(checked => !checked)}
style={{
transform: "scale(1.2)",
}}
/>
{value}
</label>
);
}
return (
<>
<form onSubmit={(e) => onSubmit(e)}>
<label>
Comment :
<Dropdown
options={COMMENT}
isMulti={false}
value={data.comment}
onChange={(e) => handleSubmit(e)}
/>
</label>
Choice : <Checkbox value={0} />
<label >
Title:
<input
type="text"
value={data.title}
placeholder="Title"
onChange={(e) => handleSubmit(e)}
id="title"
/>
</label>
<label >
Date:
<div>
<input
type="date"
value={data.date}
onChange={(e) => handleSubmit(e)}
id="date"
/>
</div>
</label>
<button>Submit</button>
</form>
</>
);
}
My goal is to be able when I click on 'Confirm' button (StepperControl.jsx) to send all the data to my api (and not each time i click on Next, but I'm aware that I will have to put an url, for now it will not work but that's fine, when i will give it one, it will work afterwards)
Here is my code
The problem is that you console.log the actual change event. Change console.log(e); in handleSubmit method to console.log(newData) to see the new data. Also to avoid confusion, I suggest to change this method name (handleSubmit method), as it is not a submit event handler but a change event handler - the submit method that sends the data is onSubmit.
Another issue I noticed - choice dropdown is not controlled so data.choice will never change.
The problem is that you're console.loging e. You need to console.log data or newData to get the output that you are looking for. e is the click event, so it shows all of the properties on the event (target, etc.), not the actual data.

React Input Warning: A component is changing a controlled input of type text to be uncontrolled

I am practicing REST API by using one Fake API site. For front-end, I am using React typescript and React router dom for routing. I successfully login the email and password by using Fake API's login and redirect to list users, where I fetched the data from Fake API and shows the user's name, image. I used the edit button, after clicking the button it will redirect to my Update components where it will populate the input field then I will update the data. My update components work fine as expected but in my console, I am getting a warning as soon as I type my input field.Here is the Error visualization
This is React Update components
import React, { useState, useEffect } from "react";
import axios from "axios";
const Update = props => {
const [state, setState] = useState({
first_name: "",
last_name: "",
email: ""
});
const [loading, setLoading] = useState(false);
useEffect(() => {
axios
.get("https://reqres.in/api/users/" + props.match.params.id)
.then(response => {
setState({
first_name: response.data.data.first_name,
last_name: response.data.data.last_name,
email: response.data.data.email
});
})
.catch(function(error) {
console.log(error);
});
}, [props.match.params.id]);
const onChangeFirstName = e => {
setState({
first_name: e.target.value
});
};
const onChangeLastName = e => {
setState({
last_name: e.target.value
});
};
const onChangeEmail = e => {
setState({
email: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
setLoading(true);
const obj = {
first_name: state.first_name,
last_name: state.last_name,
email: state.email
};
axios
.patch("https://reqres.in/api/users/" + props.match.params.id, obj)
.then(res => console.log(res.data));
setLoading(false);
props.history.push("/users");
};
return (
<div>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>First Name: </label>
<input
type="text"
className="form-control"
value={state.first_name}
onChange={onChangeFirstName}
id="first_name"
/>
</div>
<div className="form-group">
<label>Last Name: </label>
<input
type="text"
className="form-control"
value={state.last_name}
onChange={onChangeLastName}
id="last_name"
/>
</div>
<div className="form-group">
<label>Email: </label>
<input
type="email"
className="form-control"
value={state.email}
onChange={onChangeEmail}
id="email"
/>
</div>
<div className="form-group">
<button
className="btn waves-effect blue lighten-1"
type="submit"
name="action"
disabled={loading}
>
{loading ? "loading..." : "save"}
</button>
</div>
</form>
</div>
);
};
export default Update;
With hooks, when you set the state of an object, you need to merge all the properties by yourself. In other words, if you update a property of an object with state updater, the remaining properties of the objects are not merged by themselves unlike this.setState in class components.
Modify your onChange to like this:
const onChangeFirstName = e => {
const val = e.target.value;
setState(prevState => ({
...prevState,
first_name: val
}));
};
See working demo
Also quick suggestion:
Instead of writing multiple onChanges, you can simplify and just use one.
Like this:
<input
type="text"
className="form-control"
value={state.first_name}
onChange={onChange}
id="first_name"
name="first_name" />
...
const onChange = e => {
const {name, value} = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};

Resources