ReactJS | Consolidate Form onChange Handlers - reactjs

So check out this one for more details on original problem: Reactjs Object State Change Events in Form
Now I'd really like to consolidate my handlers, I've ended up with a bunch of inline onChange handlers, which if I want to extract and re-use my code is not really acheivable unless I can consolidate them into a single handler. How would one go about consolidating these into a single function which can handle everything?
<Container>
<h2>Create New Facility</h2>
<p>facility name: {Facility.Name}</p>
<br></br>
<p>address: {Facility.AddressLineOne}</p>
<Form onSubmit={handleSubmit}>
<Row className="mb-3">
<Col>
<Form.Label>Location Name</Form.Label>
<Form.Control
type="text"
placeholder="Location's Name"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
Name: e.target.value,
}))
}
/>
</Col>
<Col>
<Form.Label>Latitude</Form.Label>
<Form.Control
type="float"
placeholder="Enter Latitude"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
Latitude: e.target.value,
}))
}
/>
</Col>
<Col>
<Form.Label>Longitude</Form.Label>
<Form.Control
type="float"
placeholder="Enter Longitude"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
Longitude: e.target.value,
}))
}
/>
</Col>
</Row>
<Row className="mb-3">
<Col>
<Form.Label>Description</Form.Label>
<Form.Control
as="textarea"
rows={3}
placeholder="Enter Description"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
Description: e.target.value,
}))
}
/>
</Col>
</Row>
<Form.Group className="mb-3" controlId="formGridAddress1">
<Form.Label>Address</Form.Label>
<Form.Control
placeholder="1234 Main St"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
AddressLineOne: e.target.value,
}))
}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="formGridAddress2">
<Form.Label>Address 2</Form.Label>
<Form.Control
placeholder="Apartment, studio, or floor"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
AddressLineTwo: e.target.value,
}))
}
/>
</Form.Group>
<Row className="mb-3">
<Form.Group as={Col} controlId="formGridCity">
<Form.Label>City</Form.Label>
<Form.Control
placeholder="Miami Beach"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
City: e.target.value,
}))
}
/>
</Form.Group>
<Form.Group as={Col} controlId="formGridState">
<Form.Label>State</Form.Label>
<Form.Control
placeholder="FL"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
State: e.target.value,
}))
}
/>
</Form.Group>
<Form.Group as={Col} controlId="formGridZip">
<Form.Label>Zip</Form.Label>
<Form.Control
placeholder="33141"
onChange={(e) =>
setFacility((oldValue) => ({
...oldValue,
ZipCode: e.target.value,
}))
}
/>
</Form.Group>
</Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Container>

Well, you can implement the hook that manages the form state and allows you to register new fields:
function useForm({ defaultValues = {} }) {
const [ values, setValues ] = useState(defaultValues);
const field = (name) => {
const onChange = (e) => {
setValues((current) => ({ ...current, [name]: e.target.value }));
};
const value = values[name];
return { name, onChange, value };
};
return { field, values };
}
Then apply it to the form
function FacilityForm() {
const { values, field } = useForm({});
const handleSubmit = () => {
console.log(values);
};
return (
<Form onSubmit={handleSubmit}>
<Form.Label>Location Name</Form.Label>
<Form.Control
{...field('location')}
type="text"
placeholder="Location's Name"
/>
<Form.Label>Latitude</Form.Label>
<Form.Control
{...field('latitude')}
type="text"
placeholder="Latitude"
/>
</Form>
);
}
This is a very simple implementation that may lack the features you might want to have (e.g validation, nested keys, etc.). I would recommend you consider the existing libraries for that. For example, react-hook-forms is very good for such purposes

Related

form input not resetting with useState

I have a react bootstrap number form and state.
I would like to reset hours state to 0 after performing the button click and API POST transaction.
hours state updates just fine for the onChange function where I call setHours() in form and I'm using this strategy to reset form data on other components. Any ideas why I can't get it to work elsewhere after button click (i.e. in my try/catch block)??
const [hours, setHours] = useState(0);
// ...
const handleAddWorkItem = async (sprintId, date, hours) => {
try {
await addWorkItemToAirtable(sprintId, date, hours);
const workItems = await fetchWorkItemsByJobId(sprint.job.id);
setworkItems(workItems);
setHours(0); // not working
} catch (error) {
console.log({ error });
}
};
// ...
<Form>
<Form.Group
className="mb-2"
controlId="addTimeFormEmployee"
>
<Form.Label className="mb-0">
<strong>Employee:</strong>
</Form.Label>
<Form.Select
disabled
defaultValue={sprint.employee.id}
onChange={(e) => {
setEmployee(e.target.value);
}}
aria-label="Select Employee"
>
{employees &&
employees.map((employee) => {
return (
<option key={employee.id} value={employee.id}>
{`${employee.firstName} ${employee.surname}`}
</option>
);
})}
</Form.Select>
</Form.Group>
<Form.Group className="mb-2" controlId="addTimeFormDate">
<Form.Label className="mb-0">
<strong>Date:</strong>
</Form.Label>
<Form.Control
type="text"
onFocus={(e) => (e.target.type = "date")}
onChange={(e) => {
setDate(e.target.value);
}}
defaultValue={date}
placeholder={date}
/>
</Form.Group>
<Form.Group className="mb-2" controlId="addTimeFormHours">
<Form.Label className="mb-0">
<strong>Hours:</strong>
</Form.Label>
<Form.Control
type="number"
defaultValue={0}
onChange={(e) => setHours(parseInt(e.target.value, 10))}
placeholder={0}
min={0}
/>
</Form.Group>
</Form>
<Button
variant="success"
onClick={() => {
handleAddWorkItem(sprint.id, date, hours);
}}
>
Add Time
</Button>
Thank you!

Reactjs Object State Change Events in Form

So I've got a basic form that I'm rendering and the component needs state. I would prefer for the state to be an object instead of a list of values so I can easily call JSON.stringify(stateObject). This would enable me to easily refactor into a more re-usable HOC later where I pass the state object definition & urls as a configuration.
Anyways, what happens is that every time I modify an input, it clears out the properties which were not changed and sets the property which was changed.
See initial entry below
I add input to a different input and it removes the first input and adds the second input. Notice that Facility Name is now cleared and address is now populated.
Below is the code which is doing this
import React, { useState } from "react";
import { Form, Row, Col, Button, Container } from "react-bootstrap";
function FacilitiesCreate() {
const [Name, setName] = useState("");//not ideal
const [Latitude, setLatitude] = useState("");//not ideal
const [Longitude, setLongitude] = useState("");//not ideal
const [Description, setDescription] = useState("");//not ideal
const [AddressLineOne, setAddressLineOne] = useState("");//not ideal
const [AddressLineTwo, setAddressLineTwo] = useState("");//not ideal
const [City, setCity] = useState("");//not ideal
const [State, setState] = useState("");//not ideal
const [ZipCode, setZipCode] = useState(""); //not ideal
const [Facility, setFacility] = useState({
Name: "",
Latitude: 0,
Longitude: 0,
Description: "",
AddressLineOne: "",
AddressLineTwo: "",
City: "",
State: "",
ZipCode: "",
}); //I want to be able to JSON.stringify this for my body post call :D see handleSubmit
const createFacilityEndpoint = "https://localhost:7113/api/facilities";
const handleSubmit = async (event) => {
event.preventDefault();
let response = await fetch(createFacilityEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify(Facility),
});
if (response.ok) {
alert("YAY!");
}
};
return (
<Container>
<h2>Create New Facility</h2>
<p>facility name: {Facility.Name}</p>
<br></br>
<p>address: {Facility.AddressLineOne}</p>
<Form onSubmit={handleSubmit}>
<Row className="mb-3">
<Col>
<Form.Label>Location Name</Form.Label>
<Form.Control
type="text"
placeholder="Location's Name"
onChange={(e) => setFacility({ Name: e.target.value })}
/>
</Col>
<Col>
<Form.Label>Latitude</Form.Label>
<Form.Control
type="float"
placeholder="Enter Latitude"
onChange={(e) => setLatitude(e.target.value)}
/>
</Col>
<Col>
<Form.Label>Longitude</Form.Label>
<Form.Control
type="float"
placeholder="Enter Longitude"
onChange={(e) => setLongitude(e.target.value)}
/>
</Col>
</Row>
<Row className="mb-3">
<Col>
<Form.Label>Description</Form.Label>
<Form.Control
as="textarea"
rows={3}
placeholder="Enter Description"
onChange={(e) => setDescription(e.target.value)}
/>
</Col>
</Row>
<Form.Group className="mb-3" controlId="formGridAddress1">
<Form.Label>Address</Form.Label>
<Form.Control
placeholder="1234 Main St"
onChange={(e) => setFacility({ AddressLineOne: e.target.value })}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="formGridAddress2">
<Form.Label>Address 2</Form.Label>
<Form.Control
placeholder="Apartment, studio, or floor"
onChange={(e) => setAddressLineTwo(e.target.value)}
/>
</Form.Group>
<Row className="mb-3">
<Form.Group as={Col} controlId="formGridCity">
<Form.Label>City</Form.Label>
<Form.Control
placeholder="Miami Beach"
onChange={(e) => setCity(e.target.value)}
/>
</Form.Group>
<Form.Group as={Col} controlId="formGridState">
<Form.Label>State</Form.Label>
<Form.Control
placeholder="FL"
onChange={(e) => setState(e.target.value)}
/>
</Form.Group>
<Form.Group as={Col} controlId="formGridZip">
<Form.Label>Zip</Form.Label>
<Form.Control
placeholder="33141"
onChange={(e) => setZipCode(e.target.value)}
/>
</Form.Group>
</Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</Container>
);
}
export default FacilitiesCreate;
So the above is kinda cool, except it keeps nuking the other properties in the Facility State object...
Change the following:
(e) => setFacility({ Name: e.target.value })
to:
(e) => setFacility((oldValue) => ({ ...oldValue, Name: e.target.value }))
There is an option with useState to provide a callback function to the set function. This callback gets the old value of the state as parameter, so you can use it for the new state you want to set.

Validation is not working for these and unable to write something in the forum

I am using this form from the react bootstrap. But when i am trying to validate it is not working. Can some one please help here and thanks in adavance.
but when i am trying to validate the username, email and phone, I am unable to do it also unable to write something in the forum, can some one please help me where i made exact error
import React, { useEffect, useState } from 'react'
import { Button, Col, Form, Row } from 'react-bootstrap';
const AddressValidation = () => {
const initialValues = { username: "", email: "", password: "" };
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
setFormErrors(validate(formValues));
setIsSubmit(true);
};
useEffect(() => {
console.log(formErrors);
if (Object.keys(formErrors).length === 0 && isSubmit) {
console.log(formValues);
}
}, [formErrors]);
const validate = (values) => {
const errors = {};
const regex = /^[^\s#]+#[^\s#]+\.[^\s#]{2,}$/i;
if (!values.username) {
errors.username = "Username is required!";
}
if (!values.email) {
errors.email = "Email is required!";
} else if (!regex.test(values.email)) {
errors.email = "This is not a valid email format!";
}
if (!values.username) {
errors.username= "usernameis required";
} else if (values.username.length < 4) {
errors.username= "username must be more than 4 characters and combination of 3 letters";
} else if (values.username.length > 10) {
errors.username= "username cannot exceed more than 10 characters";
}
return errors;
};
return (
<div className="container">
<pre>{JSON.stringify(formValues, undefined, 2)}</pre>
<Form onSubmit = { handleSubmit }>
<h1> Payment Validation Form</h1>
<Row className="mb-3">
<Form.Group as={Col} controlId="formGridUsername">
<Form.Label>User Name</Form.Label>
<Form.Control type="text" placeholder="Enter User Name" value={ formValues.username}
onChange = {handleChange}/>
</Form.Group>
<Form.Group as={Col} controlId="formGridEmail">
<Form.Label>Email</Form.Label>
<Form.Control type="email" placeholder="Enter email" value={ formValues.email}
onChange = {handleChange} />
</Form.Group>
<Form.Group as={Col} controlId="formGridPhone">
<Form.Label>phonenumber</Form.Label>
<Form.Control type="number" placeholder="Enter email" value={ formValues.phonenumber}
onChange = {handleChange} />
</Form.Group>
</Row>
<Form.Group className="mb-3" controlId="formGridAddress1">
<Form.Label>Address</Form.Label>
<Form.Control placeholder="1234 Main St" />
</Form.Group>
<Form.Group className="mb-3" controlId="formGridAddress2">
<Form.Label>Address 2</Form.Label>
<Form.Control placeholder="Apartment, studio, or floor" />
</Form.Group>
<Row className="mb-3">
<Form.Group as={Col} controlId="formGridCity">
<Form.Label>City</Form.Label>
<Form.Control />
</Form.Group>
<Form.Group as={Col} controlId="formGridState">
<Form.Label>State</Form.Label>
<Form.Select defaultValue="Choose...">
<option>Choose...</option>
<option>Stockholm</option>
<option>Mälmo</option>
</Form.Select>
</Form.Group>
<Form.Group as={Col} controlId="formGridZip">
<Form.Label>Zip</Form.Label>
<Form.Control />
</Form.Group>
</Row>
<Form.Group className="mb-3" id="formGridCheckbox">
<Form.Check type="checkbox" label="Check me out" />
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</div>
);
}
export default AddressValidation
You need to add the name property to your inputs. Otherweise you get an empty name property from event.target:
<Form.Control
name="username"
type="text"
placeholder="Enter User Name"
value={formValues.username}
onChange={handleChange}
/>
Your validation seems to work. When I try to submit, I can read the error messages in console:
{username: 'usernameis required', email: 'Email is required!'}

How to add edit button and function in react.js

i want to share this question. So, i have a form which is designed by react-bootstrap. And also use React Hooks and React Function Component. I have a form which is add new form and delete form but i don't do edit form.
This is a return statement
return(
<Container>
<Row>
<Col>
<Form onSubmit={handleSubmit}>
<Form.Group>
<Form.Label>Name</Form.Label>
<Form.Control ref = {firstname} type="text" placeholder="Name.." />
</Form.Group>
<Form.Group>
<Form.Label>Surname</Form.Label>
<Form.Control ref = {secondname} type="text" placeholder="Surname.." />
</Form.Group>
<Form.Group>
<Form.Label>Email address</Form.Label>
<Form.Control ref = {email} type="email" placeholder="E-Mail" />
<Form.Text> Please, Enter like "asd#asd.com"</Form.Text>
</Form.Group>
<Form.Group>
<Form.Label>Comment</Form.Label>
<Form.Control ref = {comment} as="textarea" rows={3} placeholder = "Notes :)"/>
</Form.Group>
<Button className = "btn-lg" onClick={handleSubmit} variant="success" type="submit">Submit</Button>
</Form>
</Col>
</Row>
{Formss}
</Container>
)
And then, These are the function of this return
const Formss = input.map((item , index) =>
{
return(
<Lists key = {index} item = {item} index = {index} deleteFunc={handleDelete}/>
)
}
)
const handleSubmit = (event) => {
event.preventDefault();
const name = firstname.current.value
const surname = secondname.current.value
const mail = email.current.value
const mycomment = comment.current.value
const data = {id:id(),
name : name,
surname : surname,
mail : mail,
mycomment : mycomment}
if(data.name && data.surname && data.mail && data.mycomment){
setInput([...input, data])
firstname.current.value = ""
secondname.current.value = ""
email.current.value = ""
comment.current.value =""
}else{
console.log("oopss")
}
}
I use ref hook for handleSubmit. So, How to add edit button and edit function?
To be able to edit data, and to save it in state you can do it as in provided example. Then in handleSubmit function you can process your data further:
import React from "react";
import { Container, Row, Col, Form, Button } from "react-bootstrap";
const App = () => {
const handleSubmit = (e) => {
e.preventDefault();
console.log(state);
};
const initialState = {
firstname: "",
secondname: "",
email: "",
comment: "",
};
const [state, setState] = React.useState(initialState);
const handleChange = ({ target: { value, name } }) => {
setState({ ...state, [name]: value });
};
return (
<Container>
<Row>
<Col>
<Form onSubmit={handleSubmit}>
<Form.Group>
<Form.Label>Name</Form.Label>
<Form.Control
name="firstname"
value={state.firstname}
type="text"
placeholder="Name.."
onChange={handleChange}
/>
</Form.Group>
<Form.Group>
<Form.Label>Surname</Form.Label>
<Form.Control
name="secondname"
value={state.secondname}
type="text"
placeholder="Surname.."
onChange={handleChange}
/>
</Form.Group>
<Form.Group>
<Form.Label>Email address</Form.Label>
<Form.Control
value={state.email}
name="email"
type="email"
placeholder="E-Mail"
onChange={handleChange}
/>
<Form.Text> Please, Enter like "asd#asd.com"</Form.Text>
</Form.Group>
<Form.Group>
<Form.Label>Comment</Form.Label>
<Form.Control
name="comment"
value={state.comment}
as="textarea"
rows={3}
placeholder="Notes :)"
onChange={handleChange}
/>
</Form.Group>
<Button className="btn-lg" variant="success" type="submit">
Submit
</Button>
</Form>
</Col>
</Row>
</Container>
);
};
export default App;

How to add validation in Product add Form c- react component

I am doing Form for add product to firebase, but each click on the submit button, although it already has data or not, it still can add in Firebase. I would like to add validation to this case. How could I do that. I would like all file input cannot be left blank, and text file is always text not the number
const ProductForm = ({
products,
createProductRequest,
fetchProductRequest,
loading,
type }) => {
const [values, setValues] = useState({
image: "",
name: "",
price: 0,
description: "",
categoty: "",
});
const [imageAsFile, setImageAsFile] = useState();
useEffect(() => {
if (Object.keys(products).length) {
setValues(products);
}
}, [products]);
useEffect(() => {
if (type === "CREATE_PRODUCT_SUCCESS") {
fetchProductRequest();
}
}, [fetchProductRequest, type]);
const handleInputChange = (event) => {
// Create new product to update
const newPropdudct = {
...values,
[event.target.name]: event.target.value,
};
// Update new product for value
setValues(newPropdudct);
};
const handleFileChange = (event) => {
const image = event.target.files[0]
setImageAsFile(imageAsFile => (image))
console.log(image);
}
const onSave = () => {
createProductRequest(values, imageAsFile);
};
if (loading) {
return (
<Container>
<Loading />
</Container>
);
}
return (
<Container className="product-form">
<Form>
<Form.Group>
<Form.Group>
<Form.File
id="image"
label="Image choose"
value={values.image.name}
onChange={handleFileChange}
/>
</Form.Group>
</Form.Group>
<Form.Group controlId="name">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter product name"
value={values.name}
name="name"
onChange={handleInputChange}
>
</Form.Control>
</Form.Group>
<Form.Group controlId="categories">
<Form.Label>Categories</Form.Label>
<Form.Control
as="select"
name="category"
value={values.category}
onChange={handleInputChange}
>
{CATEGORIES.map((category, index) => (
<option key={index}>{category.name}</option>
))}
</Form.Control>
</Form.Group>
<Form.Group controlId="price">
<Form.Label>Price</Form.Label>
<Form.Control
type="number"
placeholder="Enter product price"
value={values.price}
name="price"
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="description">
<Form.Label>Description</Form.Label>
<Form.Control
as="textarea"
rows="3"
name="description"
value={values.description}
onChange={handleInputChange}
/>
</Form.Group>
<Button btnText="Submit" size="btn-md" handleClick={onSave} />
</Form>
</Container>
);
};
export default ProductForm;
You may start by adding the required attribute to each form control for basic HMTL5 validation.
For more advanced validation logic, you can have this as your onSave content:
const onSave = (event) => {
let invalid = [];
['name', 'description', 'category'].forEach(key => {
if (values[key].match(/[0-9]+/))
invalid.push(key);
});
if (values.name.trim() === '')
invalid.push('name');
if (invalid.length !== 0) {
event.preventDefault();
// Whatever you want to do when the content is invalid
} else {
createProductRequest(values, imageAsFile);
}
};
With this snippet, you have the invalid fields in the invalid array variable.
You can also customize the validation logic.

Resources