how to avoid repeating pattern when creating form in react - reactjs

I am going to update the form with each keystroke with useState Hook this way I have to add an onChange event listener plus a function for each input and as you can imagine it's going to be lots of functions how can I avoid repeating myself?
function firstNamInput(){
}
function lastNameInput(){
}
function emailInput(){
}
function phoneInput(){
}
function addressInput(){
}
function genderInput(){
}
function jobInput(){
}
function ageInput(){
}

in react we try to collect form data on every keystroke but in the past, we used to collect form data when submit clicked. so because of this new approach, we have to listen for every keystroke on every input! isn't this crazy? yes, kind of but there is a way to go around I am going to explain it to you :
first I am going to talk about Rules you have to know about inputs:
we are going to use a useState Hook and we pass an object to it which contain:
a property for every input that's single
a property for group inputs (for example if there is checkboxes for gender we create only one property for gender)
what attributes every input should have?
name (it's going to be equal to its property in the useState)
value or checked (as a rule of thumb if the inputs gets true or false its usually checked vice versa )
onChange event
so let's create useState I am going to have a total of 7 properties in the object:
const [formData, setFormData] = React.useState(
{
firstName: "",
lastName: "",
email: "",
comments: "",
isFriendly: true,
employment: "",
favColor: ""
}
)
we need to create a function for the onChange event i am going to make it as reusable as possible it's going to get form data from each input and update it in our Hook.
function handleChange(event) {
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
now we are going to add inputs look at note 2 again and now you are ready
import React from "react"
export default function Form() {
const [formData, setFormData] = React.useState(
{
firstName: "",
lastName: "",
email: "",
comments: "",
isFriendly: true,
employment: "",
favColor: ""
}
)
function handleChange(event) {
const {name, value, type, checked} = event.target
setFormData(prevFormData => {
return {
...prevFormData,
[name]: type === "checkbox" ? checked : value
}
})
}
function handleSubmit(event) {
event.preventDefault()
// submitToApi(formData)
console.log(formData)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="First Name"
onChange={handleChange}
name="firstName"
value={formData.firstName}
/>
<input
type="text"
placeholder="Last Name"
onChange={handleChange}
name="lastName"
value={formData.lastName}
/>
<input
type="email"
placeholder="Email"
onChange={handleChange}
name="email"
value={formData.email}
/>
<textarea
value={formData.comments}
placeholder="Comments"
onChange={handleChange}
name="comments"
/>
<input
type="checkbox"
id="isFriendly"
checked={formData.isFriendly}
onChange={handleChange}
name="isFriendly"
/>
<label htmlFor="isFriendly">Are you friendly?</label>
<br />
<br />
<fieldset>
<legend>Current employment status</legend>
<input
type="radio"
id="unemployed"
name="employment"
value="unemployed"
checked={formData.employment === "unemployed"}
onChange={handleChange}
/>
<label htmlFor="unemployed">Unemployed</label>
<br />
<input
type="radio"
id="part-time"
name="employment"
value="part-time"
checked={formData.employment === "part-time"}
onChange={handleChange}
/>
<label htmlFor="part-time">Part-time</label>
<br />
<input
type="radio"
id="full-time"
name="employment"
value="full-time"
checked={formData.employment === "full-time"}
onChange={handleChange}
/>
<label htmlFor="full-time">Full-time</label>
<br />
</fieldset>
<br />
<label htmlFor="favColor">What is your favorite color?</label>
<br />
<select
id="favColor"
value={formData.favColor}
onChange={handleChange}
name="favColor"
>
<option value="red">Red</option>
<option value="orange">Orange</option>
<option value="yellow">Yellow</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
<option value="indigo">Indigo</option>
<option value="violet">Violet</option>
</select>
<br />
<br />
<button>Submit</button>
</form>
)
}

Related

using useState to change get the input:text and using onclick to print new <p>

i'm a beginner in React and trying to learn useState. and I have difficulties on how to get the value of input and to save the value and print it on button click
const HomePage = () => {
const [state, setState] = useState({
Name: "",
surName: "",
});
const handleChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value,
});
};
const RenderNameOC = () => {
return (
<p>
Halo {Name} {surName}
</p>
);
};
return (
<DivContainer>
<ContainerTitle>
<p>Exercise 2 - Form</p>
</ContainerTitle>
<InputContainer>
<InputArea>
<label>Name: </label>
<input type="text" value={state.Name} onChange={handleChange} />
</InputArea>
<InputArea>
<label>Surname: </label>
<input type="text" value={state.surName} onChange={handleChange} />
</InputArea>
<SubmitButton onClick={RenderNameOC}>Submit</SubmitButton>
</InputContainer>
</DivContainer>
);
};
export default HomePage;
this is my code right now and the error it gave me was 'name' and 'surname' is not defined.
my expected result is that there will be 2 input textbox with for name and surname. and when the button is clicked, it will add a new <p> below it.
Here should be state.Name and state.surName
<p>
Halo {state.Name} {state.surName}
</p>
And add name in both inputs
<input
type="text"
name="Name"
value={state.Name}
onChange={handleChange}
/>
<label>Surname: </label>
<input
type="text"
name="surName"
value={state.surName}
onChange={handleChange}
/>
But no point of returning anything RenderNameOC since onClick is a void function. Just move this template below the submit button
Demo

React How do I extract data from a form when triggering onSubmit event?

I am messing around with Riot's API that allows getting information of a player by the player's name. I am trying to get an API key (I only got a 24 hour key) and the target player's name from users' input.
export function PlayerSearch() {
function handlesubmit(e) {
console.log(e.target);
e.preventDefault();
}
return (
<div className='player'>
<div className='inputfield'>
<form onSubmit={handlesubmit} method='GET' autoComplete="off">
<div>
<label htmlFor="key">Your API key:</label>
<input
placeholder='Your API key'
onFocus={(e)=>{e.target.placeholder=''}}
type="text"
id="key"
name="key" />
</div>
<div>
<label htmlFor="name">Player name:</label>
<input
placeholder='Player name'
onFocus={(e)=>{e.target.placeholder=''}}
type="text"
id="name"
name="name" />
</div>
<div>
<input type='submit' />
</div>
</form>
</div>
</div>
);
}
And I got this in the console:
So how exactly do I extract the two inputs from the form?
In addition, is it possible that I can call another component and pass data as props to handle the submitted data so that the code is cleaner?
You should have a state to store your inputs:
const [formData, setFormData] = useState({ key: "", name: "" });
Then you need a function that gets called onChange of your input fields to update your state:
const onChange = (event) => {
setFormData({ ...formData, [event.target.name]: event.target.value });
};
And you need to pass that function to your input onChange property:
<input
placeholder="Your API key"
onFocus={(e) => {
e.target.placeholder = "";
}}
type="text"
name="key"
value={formData.key}
onChange={onChange}
/>
Then you can access the state in your handleSubmit:
function handlesubmit(e) {
console.log(formData);
e.preventDefault();
}

React/Hooks extra undefined field upon submission

I'm getting a really weird return error that on submission I randomly add an extra duplicate field somehow which is of course then undefined. The input value is also randomly copied from one of the other fields within the form.
const GameForm = () => {
const url = 'http://localhost:6060/games'
const handleInputChange = e => {
const { name, value, year, rating, developer } = e.target
setGameData({ ...gameData, [name]: value, [year]: value, [rating]: value, [developer]: value })
}
const onSubmit = (e) => {
e.preventDefault()
const { name, year, rating, developer } = gameData
if (!name || !year || !rating || !developer) return
console.log(gameData)
axios
.post(url, gameData)
.then((res) => {
setGameData(res)
}).catch((error) => console.log(error))
}
const [gameData, setGameData] = useState({ name: '', year: 0, rating: '', developer: "" })
return (
<form onSubmit={onSubmit}>
<label id="name" htmlFor="name">Name: </label>
<input type="text" name="name" placeholder="Game title" onChange={handleInputChange} />
<br />
<label htmlFor="year">Year: </label>
<input type="number" name="year" placeholder="Release year" onChange={handleInputChange} />
<br />
<label htmlFor="rating">Rating: </label>
<input type="text" name="rating" placeholder="Age rating" onChange={handleInputChange} />
<br />
<label htmlFor="developer">Developer: </label>
<input type="text" name="developer" placeholder="Developer" onChange={handleInputChange} />
<br />
<input type="submit" value="Submit"></input>
</form>
)
}
Console logged return: (I also get a 500 error obviously)
{name: "1", year: "1", rating: "1", developer: "1", undefined: "1"}
The undefined value is seemingly taken from any of the existing fields at random.
I feel like I am likely overlooking something obvious.
You are mis-using e.target. It will not have all the properties that you try to destruct from it. From the ones you list in your example code, it will only have name and value, all the other ones (rating, year, developer) will be undefined as they are not part of the event's target property.
The reason you only get one undefined value in your state object is because it keeps overriding itself when you set your state.
The property from the event target you are trying to access is name, so in your case basically e.target.name
Anyway, with that in mind the fix for your app will be quite simple:
const handleInputChange = e => {
const { name, value } = e.target
// Note: The name will hold whatever value of the name prop you put on your input.
// When you are editing the input with the name prop set to name, it will be "name"
// For the input with the name prop set to "year", it will be "year:
// For the input with the name prop set to "developer" it will be "developer" and so on.
setGameData({ ...gameData, [name]: value })
}
Here is a demo for you with the fix:
const App = () => {
const [gameData, setGameData] = React.useState({ name: '', year: 0, rating: '', developer: "" })
const handleInputChange = e => {
const { name, value } = e.target
setGameData({ ...gameData, [name]: value })
}
return (
<div>
<label id="name" htmlFor="name">Name: </label>
<input type="text" name="name" placeholder="Game title" onChange={handleInputChange} />
<br />
<label htmlFor="year">Year: </label>
<input type="number" name="year" placeholder="Release year" onChange={handleInputChange} />
<br />
<label htmlFor="rating">Rating: </label>
<input type="text" name="rating" placeholder="Age rating" onChange={handleInputChange} />
<br />
<label htmlFor="developer">Developer: </label>
<input type="text" name="developer" placeholder="Developer" onChange={handleInputChange} />
<br />
<button onClick={() => console.warn('GAME DATA OBJECT', gameData)}>Click</button>
</div>
)
}
ReactDOM.render(
<App />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

Date object from react-datepicker not getting saved

Apologies in advance for the abomination of a code you are about to see...
I am relatively new to React and programming in general, and I'm trying to create a MERN application with react-hooks-form to streamline the process. The component I have issues with is the editing portion. I was unable to figure out how to handle controlled inputs in hooks-form so I tried to circumvent the problem by using state to store the values in two different states, which I realize defeats the purpose of using hooks-forms.
Everything so far works fine with the exception of the dateOfBirth which is a required field. On submit, however I get a 400 error and says that dateOfBirth is required.
export default function EditMember(props) {
const [date, setDate] = useState(null);
const [member, setMember] = useState({
firstName: '',
lastName: '',
dateOfBirth: null,
gender: '',
address: '',
phoneNumber: ''
})
const onChangeDate = date => {
setDate(date)
}
useEffect(() => {
axios.get(`http://localhost:5000/members/${props.match.params.id}`)
.then(res => {
setMember({
firstName: res.data.firstName,
lastName: res.data.lastName,
dateOfBirth: Date.parse(res.data.dateOfBirth),
address: res.data.address,
phoneNumber: res.data.phoneNumber,
gender: res.data.gender
});
})
}, [])
useEffect(() => {
axios.get(`http://localhost:5000/members/${props.match.params.id}`)
.then(res => {
setDate(res.data.dateOfBirth);
})
}, []);
const { register, handleSubmit } = useForm();
const onSubmitData = data => {
const updatedMember = {
firstName: data.firstName,
lastName: data.lastName,
dateOfBirth: date,
address: data.address,
phoneNumber: data.phoneNumber,
gender: data.gender,
}
axios.post(`http://localhost:5000/members/update/${props.match.params.id}`, updatedMember)
.then(res => console.log(res.data))
}
return (
<form onSubmit={handleSubmit(onSubmitData)}>
<div>
<input
type="text"
name="firstName"
defaultValue={member.firstName}
placeholder="First name"
ref={register}
/>
<input
type="text"
name="lastName"
defaultValue={member.lastName}
placeholder="Last name"
ref={register}
/>
<span>Male</span>
<input
type="radio"
value="Male"
name="gender"
ref={register}
/>
<span>Female</span>
<input
type="radio"
value="Female"
name="gender"
ref={register}
/>
<input
type="text"
name="address"
placeholder="Address"
ref={register}
defaultValue={member.address}
<input
type="text"
name="phoneNumber"
placeholder="Phone Number"
ref={register}
defaultValue={member.phoneNumber}
/>
<DatePicker
selected = {member.dateOfBirth}
onChange = {onChangeDate}
placeholderText="Select date"
/>
<button type="submit">Edit Log</button>
</form>
)
}
Any reason as to why this occurs? Besides that, any insight into how I can refactor the code would be helpful.
In order to use react-datepicker with react-hook-form you need to utilize react-hook-form's Controller component. Reference here: Integrating Controlled Inputs.
The following component declaration illustrates wrapping a react-datepicker DatePicker component in a react-hook-form Controller component. It is registered with react-hook-form using control={control} and then renders the DatePicker in the Controller components render prop.
const { register, handleSubmit, control, setValue } = useForm();
//...
<Controller
name="dateOfBirth"
control={control}
defaultValue={date}
render={() => (
<DatePicker
selected={date}
placeholderText="Select date"
onChange={handleChange}
/>
)}
/>
The DatePicker still needs to control its value using handleChange and a date state, but we can use this same handler to update the value of the registered input for react-hook-form using setValue().
const handleChange = (dateChange) => {
setValue("dateOfBirth", dateChange, {
shouldDirty: true
});
setDate(dateChange);
};
Your full component (without API calls) might look like the following. onSubmitData() is called by react-hook-form's handleSubmit() and here logs the output of the form, including the updated DatePicker value.
Here's a working sandbox.
import React from "react";
import "./styles.css";
import { useForm, Controller } from "react-hook-form";
import DatePicker from "react-datepicker";
export default function App() {
return (
<div className="App">
<EditMember />
</div>
);
}
function EditMember() {
const { register, handleSubmit, control, setValue } = useForm();
const [date, setDate] = React.useState(new Date(Date.now()));
const onSubmitData = (data) => {
console.log(data);
// axis.post(
// `http://localhost:5000/members/update/${props.match.params.id}`,
// data).then(res => console.log(res.data))
}
const handleChange = (dateChange) => {
setValue("dateOfBirth", dateChange, {
shouldDirty: true
});
setDate(dateChange);
};
return (
<div>
<form onSubmit={handleSubmit(onSubmitData)}>
<input
type="text"
name="firstName"
placeholder="First name"
ref={register}
/>
<input
type="text"
name="lastName"
placeholder="Last name"
ref={register}
/>
<span>Male</span>
<input type="radio" value="Male" name="gender" ref={register} />
<span>Female</span>
<input type="radio" value="Female" name="gender" ref={register} />
<input
type="text"
name="address"
placeholder="Address"
ref={register}
/>
<input
type="text"
name="phoneNumber"
placeholder="Phone Number"
ref={register}
/>
<Controller
name="dateOfBirth"
control={control}
defaultValue={date}
render={() => (
<DatePicker
selected={date}
placeholderText="Select date"
onChange={handleChange}
/>
)}
/>
<button type="submit">Edit Log</button>
</form>
</div>
);
}
Output
//Output
Object {firstName: "First", lastName: "Last", gender: "Male", address: "Addy", phoneNumber: "fon"…}
firstName: "First"
lastName: "Last"
gender: "Male"
address: "Addy"
phoneNumber: "fon"
dateOfBirth: Wed Aug 19 2020 17:20:12 GMT+0100 (BST)
I did eventually figure out what was wrong. There was a typo in the update route that blocked the field from being recorded. I was able to get it working with pretty much the exact same code.
My apologies for not going through them thoroughly.

Return value from a checkbox being checked in a mixed field react form

Ideally I want to use a single handleChanges function to set state for every field in my form. Right now, I don't really know how to make it so the checkboxes return a specific value, either true or false, or maybe even a string of "yes" or "no" depending on if they are toggled. I don't know where to put the logic for it.
Here is a simplified example of my form:
const ComponentFunction = props => {
const [fields, setFields] = useState({
inputTypeText: "",
textArea: "",
selectDropdown: "",
checkbox: "",
});
const handleChanges = event => {
setField({ ...fields, [event.target.name]: event.target.value });
};
const submitForm = event => {
event.preventDefault();
props.postForm(fields);
};
return (
<>
<form onSubmit={submitForm}>
<label htmlFor="inputTypeText">Input Type Text</label>
<input
id="title"
type="text"
name="title"
onChange={handleChanges}
value={property.title}
/>
<label htmlFor="textArea">Text Area</label>
<textarea
id="textArea"
rows="4"
cols="50"
name="textArea"
value={property.textArea}
onChange={handleChanges}
/>
<label htmlFor="selectDropdown">Select Dropdown</label>
<select onChange={handleChanges} value={property.selectDropdown} name="selectDropdown" id="select">
<option value="">--Select One--</option>
<option value="Stuff">Stuff</option>
</select>
<label htmlFor="checkbox>Check Box</label>
<input
id="checkbox"
name="checkbox"
type="checkbox"
value={property.checkbox}
onChange={handleChanges}
/>
<button type="submit">Submit</button>
</form>
</>
}
I ended up solving this by adding an additional handler just for checkboxes. Not sure if this is totally kosher, but it's pretty clean and worked perfectly.
This is the handler:
const handleClick = event => {
setFields({ ...fields, [event.target.name]: (event.target.checked ? "Checked" : "Not Checked") });
};
This is the checkbox:
<label htmlFor="checkbox">Check Box</label>
<input
id="checkbox"
name="checkbox"
type="checkbox"
onClick={handleClick}
/>

Resources