Hi :) I want to create checkout form with customer's address data, but onSubmit I would like to attach order data stored in useState (cartItems). For now, I've finished react hook form with onSubmit button that console.log only form inputs.
What is the best way to do that?
const Checkout = ({ emoneyPayment, setEmoneyPayment, cartItems }: CheckoutProps) => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Order>();
I'm unfamiliar with React Hooks Form and TypeScript, but in JS / React you could wrap it in another hook:
const Checkout = ({ emoneyPayment, setEmoneyPayment, cartItems }: CheckoutProps) => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Order>();
const [cartItems, setCartItems] = useState([])
// this function will be redefined every time cartItems or handleSubmit updates
const submitForm = useCallback(() => {
handleSubmit(cartItems)
}, [cartItems, handleSubmit])
return <div onClick={submitForm}>Submit</div>
}
UPDATE:
You will also need to pass in the cartItems to the handleSubmit. This seems to be the way to do so:
const Checkout = ({ emoneyPayment, setEmoneyPayment, cartItems }: CheckoutProps) => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<Order>();
const [cartItems, setCartItems] = useState([])
const onSubmit = (data) => {
const finalData = {...data, cartItems}
alert(JSON.stringify(finalData));
// do your actual submit stuff here
};
return <form onSubmit={handleSubmit(onSubmit)}>...formcomponents...</form>
}
Related
on handleSubmit i want to send array (fetchExcluded) but object checkboxes from useState i get empty object inside handleSaveOparation function.
please tell me how can I get data inside handlesubmit
const defaultValues = {
sum: '',
};
const {
handleSubmit,
control,
watch,
setValue,
formState: { errors, isValid },
} = useForm<OperationsData>({ defaultValues, resolver: yupResolver(schema), mode: 'onSubmit' });
const [checkboxes, setCheckboxes] = useState({});
const fetchExcluded = useMemo(
() => Object.entries(checkboxes)?.map(([k, v]) => ({ id: k, included: v })),
[checkboxes],
);
console.log('fetchExcluded', fetchExcluded);//[{id:1,included:true}]
console.log('checkboxes', checkboxes);//{1:true}
const handleSaveOperation = useCallback(
handleSubmit((data) => {
const { sum } = data;
console.log('checkboxes', checkboxes);//{}
console.log('fetchExcluded', fetchExcluded);//[]
}),
[checkboxes, date, fetchExcluded, handleSubmit],
);
return (
<form onSubmit={handleSaveOperation}>...formComponents</form>
)
const { isLoading, data } = useQuery('orderData', () =>
fetch(`http://localhost:5000/data/${id}`).then(res =>
res.json()
)
)
const [value, setValue] = useState(0)
const { register, formState: { errors }, handleSubmit, reset } = useForm({ defaultValues: { quantity: value } });
if (isLoading) {return <LoadingPage />}
const { _id, name, imgURL, description, minQuantity, availableQuantity, price } = data
after doing this code i want to set setValue(minQuantity)
I am trying it different way but i am getting error , nothing see on output and didn't get anymore idea ..
In my project I am using the same logic many times when I submit a form.
Everytime I submit, I display a loader, then remove it and show errors / success.
Is there a way to make a custom hook to avoid it ?
const [form, setForm] = useForm(initialState);
const [errors, setErrors] = useState({});
const [loading, setLoading] = useState(false);
async function submit() {
setLoading(true);
setErrors({});
const request = await Api.createAccount(form);
if(request.error) {
setErrors(request.error.data);
setLoading(false);
} else {
setLoading(false);
//Do something
}
}
You can create a custome hook like this:
const useSubmit = ({ initialState }) => {
const [form, setForm] = useForm(initialState);
const [errors, setErrors] = useState({});
const [loading, setLoading] = useState(false);
async function submit({ onSuccess }) {
setLoading(true);
setErrors({});
const request = await Api.createAccount(form);
if (request.error) {
setErrors(request.error.data);
} else {
//Do something
onSuccess && onSuccess()
}
}
return { form, setForm, submit, errors, loading };
};
And use it:
const { form, setForm, submit, errors, loading } = useSubmit({ initialState });
submit({onSuccess: () => {...your code} })
I built a custom hook to handle a form. One thing I'm having trouble with is calling the validation while the input value is changing.
I have four code snippets included. The second and third are just for context to show how the complete custom hook but feel free to skip them as I'm just curious about how to implement similar functionality from snippet 1 in snippet 4.
The reason I want to do this, in addition to calling it on submit, is that if the input value becomes ' ' I would like to display the error message and when a user started typing it would go away.
This was pretty simple when I wasn't using hooks I would just call a validate function after setState like this:
const validate = (name) => {
switch(name):
case "username":
if(!values.username) {
errors.username = "What's your username?";
}
break;
default:
if(!values.username) {
errors.username = "What's your username?";
}
if(!values.password) {
errors.username = "What's your password?";
}
break;
}
const handleChange = (e) => {
let { name, value } = e.target;
this.setState({ ...values,
[name]: value
}, () => this.validate(name))
}
So now using react hooks things are not as easy. I created a custom form handler that returns values, errors, handleChange, and handleSubmit. The form handler is passed an initialState, validate function, and a callback. As of now it looks like this:
import useForm from './form.handler.js';
import validate from './form.validation.js';
const schema = { username: "", password: "" }
export default function Form() {
const { values, errors, handleChange, handleSubmit } = useForm(schema, validate, submit);
function submit() {
console.log('submit:', values);
}
return (
<form></form> // form stuff
)
}
Here's the validation file. It's simple, it just requires values for two fields.
export default function validate(values) {
let errors = {};
if(!values.username) {
errors.username = "What's your username?";
}
if(!values.password) {
errors.password = "What's your password?";
}
return errors;
}
Now here is my form handler, where I'm trying to solve this problem. I have been trying different things around calling setErrors(validate(values)) in the useEffect but can't access the input. I'm not sure, but currently, the custom hook looks like this:
import { useState, useEffect, useCallback } from 'react';
export default function useForm(schema, validate, callback) {
const [values, setValues] = useState(schema),
[errors, setErrors] = useState({}),
[loading, setLoading] = useState(false); // true when form is submitting
useEffect(() => {
if(Object.keys(errors).length === 0 && loading) {
callback();
}
setLoading(false);
}, [errors, loading, callback])
// I see useCallback used for event handler's. Not part of my questions, but is it to prevent possible memory leak?
const handleChange = (e) => {
let { name, value } = e.target;
setValues({ ...values, [name]: value });
}
const handleSubmit = (e) => {
e.preventDefault();
setLoading(true);
setErrors(validate(values));
}
return { values, errors, handleChange, handleSubmit }
}
I'm not sure if it's a good idea to set other state (errors) while in a callback to set state (values) so created a code review
As commented; you can set errors while setting values:
const Component = () => {
const [values, setValues] = useState({});
const [errors, setErrors] = useState({});
const onChange = useCallback(
(name, value) =>
setValues((values) => {
const newValues = { ...values, [name]: value };
setErrors(validate(newValues));//set other state while in a callback
return newValues;
}),
[]
);
return <jsx />;
};
Or combine values and errors:
const Component = () => {
const [form, setForm] = useState({
values: {},
errors: {},
});
const onChange = useCallback(
(name, value) =>
setForm((form) => {
const values = { ...form.values, [name]: value };
const errors = validate(values);
return { values, errors };
}),
[]
);
const { errors, values } = form;
return <jsx />;
};
I know that the whole point of react hooks are to get away from class based component and promote functional component. However, is it possible to achieve inheritance in react hooks?
For example here, I create two hooks.
1. useEmailFormatValidator of which validates if input (onChange) is in valid Email format.
2. useEmailSignupValidator inherits useEmailFormatValidator but extends it's capability to verify user can use the username when (onBlur) event happens.
The whole point of these are useEmailFormatValidator can be used in log-in form where as useEmailSignupValidator can be used in sign-up form.
Below is my code
useEmailFormatValidator
import { useState } from 'react';
export const useEmailFormatValidator = () => {
const [value, setValue] = useState('');
const [validity, setValidity] = useState(false);
const regex = /^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const inputChangeHandler = event => {
setValue(event.target.value.trim());
if (regex.test(String(event.target.value.trim()).toLowerCase())) {
setValidity(true);
} else {
setValidity(false);
}
};
return { value: value, onChange: inputChangeHandler, validity };
};
useEmailSignupValidator
import { useState } from 'react';
import { useEmailFormatValidator } from "../auth";
export const useEmailSignupValidator = () => {
const [value, setValue] = useState('');
const [validity, setValidity] = useState(false);
const emailFormatValidator = useEmailFormatValidator();
const inputChangeHandler = event => {
emailFormatValidator.onChange(event);
setValue(emailFormatValidator.value);
setValidity(emailFormatValidator.validity);
};
const verifyUserNameExists = event => {
// Verify username is availble in back-end.
};
return { value: value, onChange: inputChangeHandler, onBlur: verifyUserNameExists, validity };
};
When I run a test, below does not work as expected and 'value' and 'validity' is undefined.
const inputChangeHandler = event => {
emailFormatValidator.onChange(event);
setValue(emailFormatValidator.value);
setValidity(emailFormatValidator.validity);
};
Is there anyway to have inheritance in custom hooks? Or how do you re-use code, of which is the purpose of react hooks?
This isn't really a custom hooks thing, imho. Why not do something like this?
const isValidEmail = email => {
const regex = /^(([^<>()[\]\\.,;:\s#"]+(\.[^<>()[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regex.test(email);
};
const MyComponent = () => {
const [inputVal, setInputVal] = useState('');
return(
<div>
<input
type='text'
name='my-input'
onChange={e => {
const email = e.target.value;
if(isValidEmail(email)){
setInputVal(email);
}else{
alert('invalid email')
}
}}
/>
</div>
)
}