Simple way to upload image in next.js and using Formik - reactjs

I want to know the way which where the image can be uploaded to my project. First I want to upload one image to internal folder, second, I want to upload multi images to the same folder
this this my code:
import React from 'react'
import { useSession } from 'next-auth/react';
import Router from 'next/router';
import { forwardRef } from 'react';
import { useFormik, Formik } from 'formik';
import * as Yup from 'yup';
import { FiDollarSign } from 'react-icons/fi';
import NavbarPage from '../../components/navBar';
import Footer from '../../components/footer';
const newproduct = () => {
const { data: session, status } = useSession({
required: true,
})
const [token, setToken] = React.useState('')
React.useEffect(() => {
if(status === 'unauthenticated') Router.replace("/auth/login");
}, [status]);
if(status === 'loading'){
return <div className='h-screen w-screen flex justify-center items-center'><Spinner color="#f59e0b" className="h-12" /></div>
}
if(status === 'authenticated'){
return (
<div className=" bg-gray-700">
<div className='bg-white'>
<NavbarPage />
</div>
<div className='p-4 dashboardHeight' style={{minHeight: 'calc(100vh-206px)'}}>
<h1 className='p-6 font-bold text-4xl text-slate-50'>New Product</h1>
<div className='flex justify-center w-full'>
<Formik
initialValues= {{
title: '',
brand: '',
category: '',
price: '',
quantity: '',
mImage: '',
aImages: '',
}}
validationSchema= {validateSchema}
onSubmit={ async(values) => {
console.log(JSON.stringify(values))
// const res = await fetch("http://localhost:3030/product", {
// method: 'POST',
// body: JSON.stringify(values),
// headers: {
// "Content-Type": "application/json",
// "Authorization": `"${session.user.accessToken}"`
// }
// })
// const user = await res.json();
}}
>
{props => (
<form onSubmit={props.handleSubmit} className="w-4/5" method='post' encType='multipart/form-data'>
<Field
dot={true}
error={props.touched?.title && props.errors?.title}
label="Title (Latin)"
name="title"
onChange={props.handleChange}
type="text"
/>
<Field
dot={true}
error={props.touched?.brand && props.errors?.brand}
label="Brand (Latin)"
name="brand"
onChange={props.handleChange}
type="text"
/>
<Field
dot={true}
error={props.touched?.category && props.errors?.category}
label="Category (Latin)"
name="category"
onChange={props.handleChange}
type="select"
>
<option>-Select Product Category-</option>
<option value="Supermarket">Supermarket</option>
<option value="Fashion">Fashion</option>
<option value="Health & Beauty">Health & Beauty</option>
<option value="Baby Products">Baby Products</option>
<option value="Phones & Tablets">Phones & Tablets</option>
<option value="Home & Office">Home & Office</option>
<option value="Electronics">Electronics</option>
<option value="Computing">Computing</option>
<option value="Sporting Goods">Sporting Goods</option>
<option value="Gaming">Gaming</option>
<option value="Automobile">Automobile</option>
</Field>
<Field
dot={true}
error={props.touched?.price && props.errors?.price}
icon={<PriceIcon />}
label="Price"
name="price"
onChange={props.handleChange}
type="text"
/>
<Field
dot={true}
error={props.touched?.quantity && props.errors?.quantity}
label="Quantity"
name="quantity"
onChange={props.handleChange}
type="text"
/>
<Field
dot={true}
error={props.touched?.mImage && props.errors?.mImage}
label="Main Image"
name="mImage"
onChange={props.handleChange}
type="file"
/>
<Field
dot={false}
error={props.touched?.aImages && props.errors?.aImages}
label="Addtional Images"
name="aImages"
onChange={props.handleChange}
type="file"
// multiple="multiple"
/>
<button
className="mt-8 bg-black active:bg-gray-900 focus:outline-none text-white rounded px-4 py-1"
type="submit"
>
Add Product
</button>
</form>)}
</Formik>
</div>
</div>
<Footer/>
</div>
)
}
}
export default newproduct
// Yup validation schema
const validateSchema = Yup.object().shape({
title: Yup.string().required('Field is required'),
brand: Yup.string().required('Field is required'),
category: Yup.string().required('Field is required'),
price: Yup.string().required('Field is required'),
quantity: Yup.string().required('Field is required'),
mImage: Yup.string(),
aImages: Yup.string(),
});
/* COMPONENT LOGIC */
const style = {
dot: `after:content-['*'] after:ml-0.5 after:text-red-500`,
error: `ring-red-500 ring-1`,
disabled: `cursor-not-allowed`,
container: `relative mb-6 mt-3`,
errorMessage: `text-sm text-red-500 mt-2`,
checkboxLabel: `block overflow-hidden h-6 rounded-full bg-gray-300`,
checkboxContainer: `relative w-10 mr-2 align-middle select-none mt-2`,
iconContainer: `absolute flex border border-transparent left-0 top-0 h-full w-10`,
icon: `flex items-center justify-center rounded-tl rounded-bl z-10 text-gray-400 text-lg h-full w-full`,
checkbox: `checked:bg-blue-500 checked:right-0 focus:outline-none right-4 duration-200 ease-in absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer`,
default: `text-base relative flex flex-1 w-full mt-1 rounded-md py-2 px-4 bg-white text-gray-700 placeholder-gray-400 text-base focus:outline-none focus:ring-1 focus:border-transparent border`,
};
const Field = forwardRef(
(
{ disabled, dot, error, icon, label, name, type = 'text', ...rest },
ref,
) => {
let component;
// if you won't use select, you can delete this part
if (type === 'select') {
component = (
<select
aria-required={dot}
aria-invalid={!!error}
className={`${style.default} ${disabled ? style.disabled : ''}
${error ? style.error : 'border-gray-300'}
`}
disabled={disabled}
id={name}
name={name}
ref={ref}
{...rest}
/>
);
}
// if you won't use textarea, you can delete this part
if (type === 'textarea') {
component = (
<textarea
aria-required={dot}
aria-invalid={!!error}
className={`${style.default} ${disabled ? style.disabled : ''}
${error ? style.error : 'border-gray-300'}
`}
disabled={disabled}
id={name}
name={name}
ref={ref}
{...rest}
/>
);
}
// if you won't use checkbox, you can delete this part and the classes checkbox, checkboxContainer and checkboxLabel
if (type === 'checkbox') {
component = (
<div className={style.checkboxContainer}>
<input
aria-required={dot}
aria-invalid={!!error}
className={`${style.checkbox} ${disabled ? style.disabled : ''}`}
disabled={disabled}
id={name}
name={name}
type="checkbox"
{...rest}
/>
<span className={style.checkboxLabel} />
</div>
);
}
// if you won't use input, you can delete this part
if (type !== 'checkbox' && type !== 'select' && type !== 'textarea') {
component = (
<div className="relative">
<div className={style.iconContainer}>
<div className={style.icon}>{icon}</div>
</div>
<input
aria-required={dot}
aria-invalid={!!error}
className={`${style.default} ${icon ? 'pl-12' : ''}
${error ? style.error : 'border-gray-300'}
${disabled ? style.disabled : ''}
`}
disabled={disabled}
id={name}
name={name}
type={type}
ref={ref}
{...rest}
/>
{error && <ErrorIcon />}
</div>
);
}
return (
<div className={`${style.container} ${disabled ? 'opacity-50' : ''}`}>
<label htmlFor={name} className={`text-white ${dot && style.dot}`}>
{label}
</label>
{component}
{error && (
<span role="alert" className={style.errorMessage}>
{error}
</span>
)}
</div>
);
},
);
Field.displayName = 'Field';
const ErrorIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
fill="currentColor"
className="absolute right-2 -mt-7 text-red-500"
viewBox="0 0 1792 1792"
>
<path d="M1024 1375v-190q0-14-9.5-23.5t-22.5-9.5h-192q-13 0-22.5 9.5t-9.5 23.5v190q0 14 9.5 23.5t22.5 9.5h192q13 0 22.5-9.5t9.5-23.5zm-2-374l18-459q0-12-10-19-13-11-24-11h-220q-11 0-24 11-10 7-10 21l17 457q0 10 10 16.5t24 6.5h185q14 0 23.5-6.5t10.5-16.5zm-14-934l768 1408q35 63-2 126-17 29-46.5 46t-63.5 17h-1536q-34 0-63.5-17t-46.5-46q-37-63-2-126l768-1408q17-31 47-49t65-18 65 18 47 49z" />
</svg>
);
const LockIcon = () => (
<svg
height="20"
width="20"
stroke="currentColor"
fill="currentColor"
strokeWidth="0"
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z" />
</svg>
);
const PriceIcon = () => (
<FiDollarSign className="text-3xl"/>
);
const Spinner = ({ color, className }) => (
<svg
fill={color}
viewBox="0 0 1792 1792"
className={`${className} flex-no-shrink animate-spin`}
xmlns="http://www.w3.org/2000/svg"
>
<path d="M1760 896q0 176-68.5 336t-184 275.5-275.5 184-336 68.5-336-68.5-275.5-184-184-275.5-68.5-336q0-213 97-398.5t265-305.5 374-151v228q-221 45-366.5 221t-145.5 406q0 130 51 248.5t136.5 204 204 136.5 248.5 51 248.5-51 204-136.5 136.5-204 51-248.5q0-230-145.5-406t-366.5-221v-228q206 31 374 151t265 305.5 97 398.5z" />
</svg>
);
I tried using FileReader but I didn't get the way and it's hard to handle it with Formik, what is the simplest and best way to implement this

well, you can upload an image to your project using the File input element in your form and handle the file in your form submission handler.
Here is an example of how you can modify your code to achieve this:
Add a File input element to your form:
<input
name="mImage"
type="file"
onChange={(event) => {
// handle the file input change event
}}
/>
then, in your form submission handler, get the file from the event.target.files property of the input element:
onSubmit={(event) => {
event.preventDefault();
const file = event.target.files[0];
// do something with the file, such as uploading it to a server
}}
To upload multiple images, you can use the multiple attribute of the File input element and handle the files in your form submission handler in a similar way.

Related

React values not updating through when rendering to child component

Please i need help
i am a beginer using react
I find it difficult get values and i just hope my explanation is clear
What will i do or which other approch is best for this
i want to get value={} from main <Controller> and pass it through render={} to be used in the child component <PhoneInput> the value={phone}
is an object that consist of arrays
this is the content of phone
[
{
country: 'bs',
phoneNumber: '122 234 4309',
label: 'Mobile',
},
{
country: 'bs',
phoneNumber: '898 776 9087',
label: 'Work',
},
];
the best of my knowledge is what i did but not it working
My aim is to enable user to add more phone number field by their self
i want a user to be able to delete field or add more phone field
Sample Image
<Controller
control={control}
name="phones"
value={phones} //phone is an array
render={({ field: { onChange, className, value, ref } }) => ( // Transfering value from value={phones} to use as value.map not working
<div className={clsx('mt-32', className)} ref={ref}>
{value.map((item, index) => ( //the value here becomes undefined
<PhoneInput
value={item}
key={index}
onChange={(val) => {
console.log(value);
onChange(value.map((_item, _index) => (index === _index ? val : _item)));
}}
onRemove={() => {
console.log(value);
onChange(value.filter((_item, _index) => index !== _index));
}}
hideRemove={value.length === 1}
/>
))}
<Button
className="group inline-flex items-center mt-2 -ml-4 py-2 px-4 rounded cursor-pointer"
onClick={() =>
onChange([...testPhone, Model().phoneNumbers[0]])
}
>
<span className="ml-8 font-medium text-secondary group-hover:underline">
Add a phone number
</span>
</Button>
</div>
)}
/>
if i send the value directly it works fine
<Controller
control={control}
name="phones"
value={phones}
render={({ field: { onChange, className, value, ref } }) => (
// <div className={clsx('mt-32', className)} ref={ref}>
{phones.map((item, index) => ( //Using variable phone directly
<PhoneInput
value={item}
key={index}
onChange={(val) => {
console.log(phones);
onChange(phones.map((_item, _index) => (index === _index ? val : _item)));
}}
onRemove={() => {
console.log(phones);
onChange(phones.filter((_item, _index) => index !== _index));
}}
hideRemove={phones.length === 1}
/>
))}
<Button
className="group inline-flex items-center mt-2 -ml-4 py-2 px-4 rounded cursor-pointer"
onClick={() =>
onChange([...phones, Model().phoneNumbers[0]])
}
>
<span className="ml-8 font-medium text-secondary group-hover:underline">
Add a phone number
</span>
</Button>
</div>
)}
/>
Here is the complete code
import { motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import {
Button,
Card,
} from '#mui/material';
import clsx from 'clsx';
import { Controller, useForm } from 'react-hook-form';
import axios from 'axios';
import PhoneInput from '../../PhoneInput';
import Model from '../../Model';
/**
* Form Validation Schema
*/
const schema = yup.object().shape({
phone: yup.string().required('Phone is required'),
});
function Settings() {
const { control, watch, reset, handleSubmit, formState, getValues } = useForm({
mode: 'onChange',
resolver: yupResolver(schema),
});
const [phones, setPhones] = useState([]);
useEffect(() => {
axios.get('/api/v1/account').then((res) => {
if (res.data.success === true) {
setPhones(res.data.phones);
}
});
}, []);
return (
<Card
component={motion.div}
variants={IT}
className="w-full overflow-hidden w-full mb-32"
>
<div className=" p-24 w-full">
<Controller
control={control}
name="first_name"
render={({ field }) => (
<TextField
className="mt-32"
{...field}
label="First Name"
placeholder="First Name"
id="first_name"
error={!!errors.first_name}
helperText={errors?.first_name?.message}
variant="outlined"
required
fullWidth
/>
)}
/>
<Controller
control={control}
name="phones"
value={phones}
render={({ field: { onChange, className, value, ref } }) => (
<div className={clsx('mt-32', className)} ref={ref}>
{phones.map((item, index) => (
<PhoneInput
value={item}
key={index}
onChange={(val) => {
console.log(phones);
onChange(phones.map((_item, _index) => (index === _index ? val : _item)));
}}
onRemove={() => {
console.log(phones);
onChange(phones.filter((_item, _index) => index !== _index));
}}
hideRemove={phones.length === 1}
/>
))}
<Button
className="group inline-flex items-center mt-2 -ml-4 py-2 px-4 rounded cursor-pointer"
onClick={() =>
onChange([...phones, Model().phoneNumbers[0]])
}
>
<span className="ml-8 font-medium text-secondary group-hover:underline">
Add a phone number
</span>
</Button>
</div>
)}
/>
</div>
</Card>
);
}
export default Settings;
Thank you very much

After browser relode i got the user profile pic in website.how can i get it first time without browser reloding

**
import { useForm } from "react-hook-form";
import React, { useState } from "react";
import { useEffect } from "react";
import { useUpdateProfile } from "react-firebase-hooks/auth";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import {
useCreateUserWithEmailAndPassword,
useSignInWithGoogle,
} from "react-firebase-hooks/auth";
import auth, { storage } from "../../firebase.init";
import { Link, useLocation, useNavigate } from "react-router-dom";
import Loader from "../Shared/Loader";
const Register = () => {
const [image, setImage] = useState(null);
const [url, setUrl] = useState(null);
const [percentage, setPercentage] = useState("");
const [updateProfile, updating] = useUpdateProfile(auth);
const [signInWithGoogle, gUser, gLoading] = useSignInWithGoogle(auth);
const [
createUserWithEmailAndPassword,
user,
loading,
error,
] = useCreateUserWithEmailAndPassword(auth);
const navigate = useNavigate();
const { register, handleSubmit, formState } = useForm({
mode: "onChange", // I want to change it to onBlur
});
const { isValid, errors } = formState;
const handleimagechange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0]);
}
};
**useEffect(() => {
if (user?.user?.uid) {
navigate("/appiontment");
window.location.reload();
}
}, [user?.user?.uid, navigate]);
useEffect(() => {
if (gUser?.user?.uid) {
navigate("/appiontment");
}
}, [gUser?.user?.uid, navigate]);**
**useEffect(() => {
const uploadImage = () => {
// const name = new Date().getTime + image.name
const storageRef = ref(storage, image.name);
const uploadTask = uploadBytesResumable(storageRef, image);
uploadTask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
setPercentage(progress);
switch (snapshot.state) {
case "paused":
break;
case "running":
break;
default:
break;
}
},
(error) => {},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
console.log("File available at", downloadURL);
setUrl(downloadURL);
});
}
);
};
image && uploadImage();
}, [image]);**
let singninErrors;
if (error?.message === "Firebase: Error (auth/email-already-in-use).") {
singninErrors = (
<div
className="bg-red-100 rounded-lg py-5 px-6 mb-4 text-base text-red-700 mb-3"
role="alert"
>
Email already Exsist
</div>
);
console.log(error.message);
}
**const onSubmit = async (data) => {
await createUserWithEmailAndPassword(data.email, data.password, data.name);
await updateProfile({ displayName: data.name, photoURL: url });
};
if (error) {
console.log(error.message);
}**
if (loading || gLoading || updating) {
return <Loader />;
}
return (
<div className="flex h-screen justify-center items-center ">
<div className="card w-96 bg-base-100 shadow-xl">
<div className="card-body">
<div className="flex flex-col w-full border-opacity-50">
<h1 className="text-xl text-center">Sign Up</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-control w-full max-w-xs ">
<label className="label">
<span className="label-text">Name</span>
</label>
<input
{...register("name", {
required: {
value: true,
message: "Name is required",
},
})}
type="text"
placeholder="Your Name"
className="input input-bordered w-full max-w-xs"
/>
<label className="label">
{errors.name?.type === "required" && (
<span className="label-text text-red-500">
{errors.name.message}
</span>
)}
</label>
</div>
<div className="form-control w-full max-w-xs ">
<label className="label">
<span className="label-text">Email</span>
</label>
<input
{...register("email", {
required: {
value: true,
message: "Email Adress is required",
},
pattern: {
value: /^[^\s#]+#[^\s#]+\.[^\s#]+$/,
message: "please enter a valid email adress",
},
})}
type="email"
placeholder="Your Email"
className="input input-bordered w-full max-w-xs"
/>
<label className="label">
{errors.email?.type === "required" && (
<span className="label-text text-red-500">
{errors.email.message}
</span>
)}
{errors.email?.type === "pattern" && (
<span className="label-text text-red-500">
{errors.email.message}
</span>
)}
</label>
</div>
<div className="form-control w-full max-w-xs ">
<label className="label">
<span className="label-text">Password</span>
</label>
<input
{...register("password", {
required: {
value: true,
message: "password is required",
},
minLength: {
value: 6,
message: "password must be 6 character or long",
},
})}
type="password"
placeholder="Your password"
className="input input-bordered w-full max-w-xs"
/>
<label className="label">
{errors.password?.type === "required" && (
<span className="label-text text-red-500">
{errors.password.message}
</span>
)}
{errors.password?.type === "minLength" && (
<span className="label-text text-red-500">
{errors.password.message}
</span>
)}
</label>
</div>
<label className="label">Upload Your Avatar</label>
<div className="w-full bg-gray-200 rounded-full">
{percentage === 0 && (
<div
className="bg-blue-100 rounded-lg py-5 px-6 mb-4 text-base text-blue-700
mb-3"
role="alert"
>
Image Uploading please wait
</div>
)}
{percentage === 100 && url && (
<div
className="bg-green-100 rounded-lg py-5 px-6 mb-4 text-base text-green-700
mb-3"
role="alert"
>
img Uploading:{percentage}% Successfully
</div>
)}
</div>
<div className="flex justify-center">
<div className="mb-3 w-96 ">
<input
onChange={handleimagechange}
className="form-control block
w-full
px-3
py-1.5
text-base
font-normal
text-gray-700
bg-white bg-clip-padding
border border-solid border-gray-300
rounded
transition
ease-in-out
m-0
focus:text-gray-700 focus:bg-white focus:border-blue-
600 focus:outline-none"
type="file"
id="formFile"
/>
</div>
</div>
{singninErrors}
<button
disabled={!isValid || !url}
type="submit"
className="btn w-full max-w-xs">
Signup</button> </form>
<p className="font-semibold ">
<small>
already have an account ?
<Link className="text-secondary mx-2" to="/">
Please login
</Link>
</small>
</p>
<div className="divider">OR</div>
<button
onClick={() => signInWithGoogle()}
className="btn btn-outline"
>
Continue with Google
</button>
</div>
</div>
</div>
</div>
);
};
export default Register;
**

How can I display formatted_address from geocode inside my formik form?

I want to display the console.log(formatted_address) from geocode inside my formik form once the user click the use my current location button. Here is code below:
const [address, setAddress] = useState({
address: {
formatted_address: "",
latitude: "",
longitude: ""
}
})
const successPosition = (position) => {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
setAddress({ address: { ...address.address, latitude, longitude } })
// set Google Maps Geocoding API for purposes of quota management. Its optional but recommended.
Geocode.setApiKey(API_Key);
// set response language. Defaults to english.
Geocode.setLanguage("en");
// set response region. Its optional.
// A Geocoding request with region=es (Spain) will return the Spanish city.
Geocode.setRegion("ng");
// Enable or disable logs. Its optional.
Geocode.enableDebug();
// Get address from latitude & longitude.
Geocode.fromLatLng(latitude, longitude).then(
(response) => {
const formatted_address = response.results[0].formatted_address;
setAddress({
address: { ...address.address, formatted_address }
});
console.log(formatted_address)
},
(error) => {
console.error(error);
}
);
}
const getCurrentLocation = () => {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(
(pos) => successPosition(pos),
);
} else {
alert("Your Browser doesn't support location service !");
}
};
And display that formatted_address inside the formik form below in order to fill it once the users decide to use the use my current location button to fill the form. when I insert {address.formatted_address} it's saying it's not defined. I set it as the value inside the address input.
<Formik
initialValues={{
address: '',
city: '',
state: '',
country: '',
}}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2))
actions.setSubmitting(false)
}, 1000)
}}
>
{({ values, handleSubmit, handleChange, handleBlur }) => (
<Form
onSubmit={handleSubmit}
autoComplete='off'
className='text-left lg:w-full lg:py-10 lg:px-0 md:py-5 sm:py-10'
>
<div className=''>
<label htmlFor='address' className='font-semibold'>
Address
</label>
<input
type='address'
name='address'
placeholder='Enter your address'
onChange={handleChange}
onBlur={handleBlur}
value={address.formatted_address}
className='border-2 border-[#F6F6F6]
rounded w-full indent-2 lg:h-[56px] outline-none
shadow-none focus:border-[#0559FD] md:py-3 md:my-2
sm:py-3 sm:my-2'
/>
</div>
<div
className='py-5 lg:grid lg:grid-cols-3 lg:gap-5 md:grid md:grid-cols-2
md:gap-5 sm:grid sm:grid-cols-2 gap-5'
>
<div className='grid grid-cols-1 gap-2'>
<label htmlFor='city' className='font-semibold'>
City
</label>
<input
type='text'
name='cities'
placeholder='Cities'
onChange={handleChange}
onBlur={handleBlur}
value={values.cities}
className='border-2 border-[#F6F6F6] outline-none
lg:w-[150px] lg:py-3 rounded text-left
focus:border-[#0559FD] md:w-[150px] md:p-2 sm:w-[150px]
sm:py-3'
/>
</div>
<div className='grid grid-cols-1 gap-2'>
<label htmlFor='state' className='font-semibold'>
State
</label>
<Field
as='select'
name='state'
onBlur={handleBlur}
value={selectedState}
onChange={(e) => setSelectedState(e.target.value)}
className='border-2 border-[#F6F6F6] outline-none
cursor-pointer lg:w-[150px] lg:py-3 rounded text-left
focus:border-[#0559FD] md:w-[150px] md:p-2 sm:w-[150px]
sm:py-3'
>
<option>--Choose State--</option>
{availableState?.states.map((e, key) => {
return (
<option value={e.name} key={key}>
{e.name}
</option>
)
})}
</Field>
</div>
<div className='grid grid-cols-1 gap-2'>
<label htmlFor='country' className='font-semibold'>
Country
</label>
<Field
as='select'
name='country'
value={selectedCountry}
onChange={(e) => setSelectedCountry(e.target.value)}
className='border-2 border-[#F6F6F6] outline-none
cursor-pointer lg:w-[150px] lg:py-3 rounded text-left
focus:border-[#0559FD] md:w-[150px] md:p-2 sm:w-[150px]
sm:py-3'
>
<option value='country'> -- Country -- </option>
{data.countries.map((value, key) => {
return (
<option value={value.name} key={key}>
{value.name}
</option>
)
})}
</Field>
</div>
</div>
</Form>
)}
</Formik>{' '}
<div className='flex gap-1 items-center lg:mb-0'>
<button onClick={() => getCurrentLocation()}> Use my current location </button>
</div>

How to Dynamically Change Value of Formik Input Based on FieldArray Inputs

I am working on a form with formik that has an array of fields. I want to dynamically add value in total and sub total fields based on the field array.
Here is the code
Formik Form
<Formik
initialValues={addPurchasesInitialValues}
validationSchema={addPurchasesValidationSchema}
onSubmit={hanldeSubmit}
>
{(props) => (
<form>
<div className=" text-gray-700 rounded w-11/12 ml-auto mr-auto ">
<div className="mt-5 max-h-[500px] overflow-y-auto">
<span className="text-gray-600">Items Information</span>
<FieldArray
name="items"
render={(arrayHelpers) => {
const items = props.values.items;
return (
<div>
{!!items &&
items.length &&
items.map((item, index) => {
return (
<div className="flex" key={index}>
<CustomInput
name={`items.${index}.itemName`}
placeholder="Item Name"
/>
<CustomInput
name={`items.${index}.itemType`}
placeholder="Item Type"
/>
<CustomInput
type="number"
name={`items.${index}.itemRate`}
placeholder="Rate"
/>
<CustomInput
type="number"
name={`items.${index}.itemQuantity`}
placeholder="Quantity"
/>
{!!index && (
<button
className="cursor-pointer flex items-center justify-center"
type="button"
onClick={() => arrayHelpers.remove(index)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
)}
</div>
);
})}
<span
className="cursor-pointer "
onClick={() => {
arrayHelpers.push({
itemName: "",
itemType: "",
itemQuantity: "",
itemRate: "",
});
}}
>
<span className="text-sm pt-3"> + Add Item</span>
</span>
</div>
);
}}
/>
</div>
<div className="fixed mb-4 bottom-1 w-9/12">
<div className="my-3 border-t border-gray-300"></div>
<div className="flex justify-between">
<div className="flex ">
<CustomInput
type="date"
name="purchasingDate"
placeholder="Purchaing Date"
/>
<CustomInput
type="number"
name="subTotal"
placeholder="Sub Total"
disabled={true}
value={subTotal}
/>
<CustomInput
name="discount"
type="number"
placeholder="Discount"
/>
<CustomInput
type="number"
name="total"
placeholder="Total"
disabled={true}
/>
</div>
<div className=" flex justify-end items-center bg-white ">
<SubmitButton
value="Add Booking"
name="addBooking"
onClick={hanldeSubmit}
>
Add Purchase
</SubmitButton>
</div>
</div>
</div>
</div>
</form>
)}
</Formik>
Here is CustomInput
const CustomInput = ({
type = INPUT_TYPES.TEXT_INPUT,
label,
name,
placeholder = "",
disabled,
options = [],
...rest
}) => {
const { values, errors, touched, handleChange } = useFormikContext();
const value = _.get(values, name); // getting value if its a nested object
let inputComponent = {
[INPUT_TYPES.TEXT_INPUT]: (
<TextInput
value={value}
label={label}
name={name}
placeholder={placeholder}
onChange={handleChange(name)}
disabled={disabled}
{...rest}
/>
),
[INPUT_TYPES.NUMBER_INPUT]: (
<NumberInput
label={label}
name={name}
placeholder={placeholder}
onChange={handleChange(name)}
disabled={disabled}
{...rest}
/>
),
[INPUT_TYPES.DATE_INPUT]: (
<DateInput
label={label}
name={name}
placeholder={placeholder}
onChange={handleChange(name)}
/>
),
[INPUT_TYPES.SELECT]: (
<Select
placeholder={label}
label={label}
value={value}
name={name}
onChange={handleChange(name)}
options={options}
/>
),
}[type];
return (
<div>
{inputComponent}
<Error visible={touched[name]} error={_.get(errors, name)} />
</div>
);
};
here are the initialValues and Schema
import * as Yup from "yup";
export const addPurchasesInitialValues = {
items: [
{
itemName: "",
itemType: "",
itemQuantity: "",
itemRate: "",
},
],
purchasingDate: "",
};
export const addPurchasesValidationSchema = Yup.object().shape({
items: Yup.array().of(
Yup.object().shape({
itemName: Yup.string().required("Required"),
itemType: Yup.string().required("Required"),
itemQuantity: Yup.number().required("Required"),
itemRate: Yup.number().required("Required"),
})
),
purchasingDate: Yup.string().required("Required"),
});

TypeError: register is not a function in react-hook-form

I have three components for form:
CreateInvoice.js (Parent component Form)
Item.js (Child Component)
Input.js (simple input with some styling)
When I try to access the register method to the Item.js file using this code const { register } = useFormContext();, it shows me this error message
TypeError: register is not a function
Input
src/components/Input.js:5
2 |
3 | const Input = ({ inputName, readOnly, register }) => {
4 | return (
> 5 | <input {...(readOnly && { disabled: true, readOnly, value: 0 })} type="text" id={inputName} className="w-full bg-primaryOne p-3 rounded-md shadow-md border border-borderOne focus:outline-none focus:border-secondaryOne transition text-white font-bold text-xs" {...register(inputName, { required: true })} />
6 | )
7 | }
8 |
But When I directly call useForm in Item.js, it doesn't show me an error message and when I add a new item to the form, it appends an empty object to the invoices array like this.
Anyone, please help me to fix this.
Input component code(Input.js):
import React from 'react'
const Input = ({ inputName, readOnly, register }) => {
return (
<input {...(readOnly && { disabled: true, readOnly, value: 0 })} type="text" id={inputName} className="w-full bg-primaryOne p-3 rounded-md shadow-md border border-borderOne focus:outline-none focus:border-secondaryOne transition text-white font-bold text-xs" {...register(inputName, { required: true })} />
)
}
export default Input
Whole form code(CreateInvoice.js):
import React, { useState } from 'react'
import { useForm, Controller, useFieldArray, FormProvider } from "react-hook-form";
import DatePicker from 'react-datepicker'
import "react-datepicker/dist/react-datepicker.css";
import Label from './Label';
import Input from './Input';
import Item from './Item';
// import { useDispatch } from 'react-redux';
// import { createInvoice } from '../actions/invoices';
const CreateInvoice = ({ openForm, setOpenForm }) => {
const { register, control, handleSubmit, errors } = useForm();
const [newItems, setNewItems] = useState([]);
const { fields, append, remove } = useFieldArray({
control,
name: "invoices"
});
// const dispatch = useDispatch();
const onSubmit = data => {
console.log(data);
// dispatch(createInvoice(data));
};
return (
<div className={`transition ${!openForm ? 'transform translate-x-full hidden' : '-translate-x-full'}`}>
<div className="fixed top-0 left-0 flex items-center justify-center w-full h-screen z-10" onClick={() => setOpenForm(!openForm)}></div>
<div className="fixed top-0 left-0 z-20 ml-24">
<FormProvider >
<form onSubmit={handleSubmit(onSubmit)} className="w-screen max-w-2xl h-screen bg-primaryTwo p-14">
<h1 className="text-white text-2xl font-bold mb-10">Create Invoice</h1>
<div className="overflow-scroll w-full h-full flex flex-col pr-7 content-area pb-10">
<small className="text-secondaryTwo font-bold text-xs">Bill Form</small>
<div>
<Label labelName="Street Address" />
<Input inputName="streetAddress" register={register} />
</div>
<div className="flex justify-between flex-wrap">
<div>
<Label labelName="City" />
<Input inputName="city" register={register} />
</div>
<div>
<Label labelName="Post Code" />
<Input inputName="postCode" register={register} />
</div>
<div>
<Label labelName="Country" />
<Input inputName="country" register={register} />
</div>
</div>
<small className="text-secondaryTwo font-bold text-xs mt-8">Bill To</small>
<div>
<Label labelName="Client Name" />
<Input inputName="clientName" register={register} />
</div>
<div>
<Label labelName="Client Email" />
<Input inputName="clientEmail" register={register} />
</div>
<div>
<Label labelName="Street Address" />
<Input inputName="clientStreetAddress" register={register} />
</div>
<div className="flex flex-wrap justify-between">
<div>
<Label labelName="City" />
<Input inputName="clientCity" register={register} />
</div>
<div>
<Label labelName="Post Code" />
<Input inputName="clientPostCode" register={register} />
</div>
<div>
<Label labelName="Country" />
<Input inputName="clientCountry" register={register} />
</div>
</div>
<div className="flex justify-between">
<div className="w-1/2 mr-2">
<Label labelName="Invoice Date" />
<Controller
control={control}
name="paymentDue"
render={({ field }) => (
<DatePicker
className="w-full bg-primaryOne p-3 rounded-md shadow-md border border-borderOne focus:outline-none focus:border-secondaryOne transition text-white font-bold text-xs"
onChange={(date) => field.onChange(date)}
selected={field.value}
/>
)}
/>
</div>
<div className="w-1/2 ml-2">
<Label labelName="Payment Terms" />
<select className="w-full bg-primaryOne p-3 rounded-md shadow-md border border-borderOne focus:outline-none focus:border-secondaryOne transition text-white font-bold text-xs" name="Payments Term" id="Payments Term" {...register("Payments Term", { required: true })}>
<option value="1">Next 1 Day</option>
<option value="7">Next 7 Days</option>
<option value="14">Next 14 Days</option>
<option value="30">Next 30 Days</option>
</select>
</div>
</div>
<div>
<Label labelName="Descriptions" />
<Input inputName="descriptions" register={register} />
</div>
<p className="text-gray-500 text-lg mt-6 mb-2 font-bold">Item List</p>
<div>
// in this code I need help
{fields.map((invoice, index) => <Item key={invoice.id} index={index} remove={remove} />)}
</div>
<button className="w-full bg-borderOne hover:bg-primaryOne transition text-white border-none rounded-full mt-4 p-4 text-xs font-bold flex justify-center" onClick=
{e => {
e.preventDefault();
append({});
}}
>
<span className="font-semibold mr-1">+</span>Add New Item
</button>
</div>
<div className="flex justify-between py-4">jsx
<button className="rounded-full text-neutral text-xs bg-primaryOne outline-none px-8 py-4 font-bold" onClick={() => setOpenForm(!openForm)}>Discard</button>
<div className="pr-7">
<button className="rounded-full text-neutral text-xs bg-primaryOne outline-none px-8 py-4 font-bold">Save as Draft</button>
<input className="rounded-full text-neutral text-xs bg-secondaryTwo outline-none ml-2 px-8 py-4 font-bold" type="submit" value="Save & Send" />
</div>
</div>
</form>
</FormProvider>
</div >
</div >
)
}
export default CreateInvoice
whole child component(Item.js) code
import React, { useState } from 'react'
import Input from './Input'
import Label from './Label'
import { useFormContext } from "react-hook-form";
const Item = ({ index, remove }) => {
const { register } = useFormContext();
return (
<div className="flex justify-center items-end">
<div className="w-3/5">
<Label labelName="Item Name" />
<Input inputName="inputName" register={register} />
</div>
<div className="w-2/12 mx-3">
<Label labelName="Qty." />
<Input inputName="quantity" register={register} />
</div>
<div className="w-1/3">
<Label labelName="Price" />
<Input inputName="price" register={register} />
</div>
<div className="mx-3">
<Label labelName="Total" />
<Input inputName="total" register={register} readOnly />
</div>
<button className="mb-4" aria-label="delete button" onClick={
e => {
e.preventDefault();
remove(index);
}}
>
<svg className="transition fill-current text-gray-400 hover:fill-current hover:text-red-400" width="13" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M11.583 3.556v10.666c0 .982-.795 1.778-1.777 1.778H2.694a1.777 1.777 0 01-1.777-1.778V3.556h10.666zM8.473 0l.888.889h3.111v1.778H.028V.889h3.11L4.029 0h4.444z" fillRule="nonzero" /></svg></button>
</div>
)
}
export default Item
You're missing to spread the formMethods to the <FormProvider /> in your <CreateInvoice /> component.
const CreateInvoice = ({ openForm, setOpenForm }) => {
const formMethods = useForm();
const { register, control, handleSubmit, formState: { errors } } = formMethods;
return (
<FormProvider {...formMethods} >
...
</FormProvider>
);
}
For the second issue:
you're not registering the field array item <input /> components correctly and miss to set the index, so RHF can't setup the link to this fields. Check the demo, i just passed the fieldId as a prop to your <Item /> component.
since v7 RHF's errors object is a property of the formState property returned by useForm. I updated the code example above.
you should set the defaultValues for your field array item when you're calling append. From the docs:
When you append, prepend, insert and update the field array, the obj can't be empty object rather need to supply all your input's defaultValues.

Resources