How to render a component inside Formik form - javascript

I'm trouble and needing help from someone more senior.
I need to render a modal component when the user clicks in a link inside a Formik form. But that form already is a component, so I'm trying to render a component inside another component that is inside a bigger component. But isn't working.
The modal or still open forever (from loading the page) or didn't load at all.
Here's the code that I tried.
Form:
// Dependencies
// $FlowExpectedError[cannot-resolve-module] */
import { useRouter } from "next/router";
// $FlowExpectedError[cannot-resolve-module] */
import { AnimatePresence, motion } from "framer-motion";
// $FlowExpectedError[cannot-resolve-module] */
import { setCookie } from "nookies";
import { Modal } from "../components/Common";
// Components
// $FlowExpectedError[cannot-resolve-module] */
import { ErrorMessage, Formik, Field, Form } from "formik";
import {
Button,
Label,
FormField,
FormCheckbox,
HeadLogo,
} from "../components/Common";
// Helpers
import { axiosRequest } from "../helpers/axiosRequest";
import React, { useState } from "react";
import literals from "../utils/literals";
const GENDERS = [
{
label: "Female",
val: "female",
},
{
label: "Male",
val: "male",
},
{
label: "Other",
val: "other",
},
];
const ModalCloseButton = ({ closeCallback }) => (
<div>
<button
type="button"
className="float-right p-5 focus:outline-none"
onClick={closeCallback}
>
{/* $FlowExpectedError[cannot-resolve-name] */}
<img
src="/icons/close.svg"
alt="Close icon"
className="float-right w-4"
/>
</button>
</div>
);
const TermsModal = ({ closeCallback }) => (
// $FlowExpectedError[cannot-resolve-name]
<>
<ModalCloseButton closeCallback={closeCallback} />
<span className="px-5 pb-5 text-lg font-bold leading-none">
{literals.SEND_TO_FRIEND}
</span>
<div className="botton justify-center">
<Button label={literals.ACCEPT} onClick={closeCallback} style="icon" />
</div>
</>
);
const SignupForm = ({
validateForm,
submitForm,
setCheckedGender,
checkedGender,
openModal,
cancelModal,
}) => (
<Formik
initialValues={{
name: "",
surnames: "",
email: "",
phone: "",
birthDate: "",
gender: "",
password: "",
terms: false,
}}
validate={validateForm}
onSubmit={submitForm}
>
{({ isSubmitting, setFieldValue, values }) => {
function handleGenderSelection(value) {
setCheckedGender(value);
setFieldValue("gender", value);
}
return (
<Form className="flex flex-col w-full space-y-2">
{/* $FlowExpectedError[cannot-resolve-name] */}
<FormField
name="name"
inputType="text"
className="text-input-bis focus:outline-none"
inputPlaceholder={literals.NAME}
/>
<FormField
name="surnames"
inputType="text"
className="text-input-bis focus:outline-none"
inputPlaceholder={literals.SURNAME}
/>
<FormField
name="email"
inputType="email"
className="text-input-bis focus:outline-none"
inputPlaceholder={literals.EMAIL}
/>
<FormField
name="password"
inputType="password"
className="text-input-bis focus:outline-none"
inputPlaceholder={literals.PASSWORD}
/>
<FormField
name="phone"
inputType="tel"
className="text-input-bis focus:outline-none"
inputPlaceholder={literals.PHONE_NUMBER}
/>
<FormField
name="birthDate"
inputType="date"
className="text-input-bis focus:outline-none"
inputPlaceholder={literals.BIRTHDATE}
/>
<div role="group" className="signup-radio">
{GENDERS.map((gender) => {
return (
<label
key={gender.val}
className={`
px-4 pt-3 pb-2 mr-2 border border-white rounded-gender leading-snug transition-colors duration-200
${
checkedGender && gender.val === checkedGender
? "bg-lightBlue"
: "transparent"
}
`}
>
<Field
type="radio"
name="gender"
value={gender.val}
checked={checkedGender === gender.val}
onChange={() => {
handleGenderSelection(gender.val);
}}
/>
<p className="text-sm text-white uppercase">{gender.label}</p>
</label>
);
})}
<ErrorMessage
name="gender"
component="div"
className="error-message"
/>
</div>
<div className="flex flex-row items-center justify-start">
<FormCheckbox
label={
<p className="ml-2 text-sm text-lightBlue">
{literals.ACCEPT_THE}
<a
href="#"
className="text-white underline"
onClick={cancelModal()}
>
{literals.TERMS_TITLE}
</a>
</p>
}
name="terms"
callback={() => setFieldValue("terms", !values.terms)}
callbackValue={!values.terms}
fillColor="lightBlue"
value={values.terms}
marginBottom="4"
/>
</div>
<div className="pb-4">
<Button type="submit" label={literals.SIGNUP} />
</div>
{/* <div className="flex flex-col items-stretch justify-start h-vh-5 flex-1 px-5 pb-5">
<div className="flex flex-col flex-1 mt-12">
<a
href={literals.GLOBAL_TERMS}
target="_blank"
className="text-xl font-bold leading-tight"
>
<u>Download</u> our Terms & Conditions
</a>
</div>
<div className="botton justify-center">
<Button
label={literals.ACCEPT}
onClick={() => setShowModal(false)}
style="icon"
/>
</div>
</div>
</Modal>
</AnimatePresence> */}
</Form>
);
}}
</Formik>
);
function Signup(): React$Node {
const [checkedGender, setCheckedGender] = useState();
const [showModal, setShowModal] = useState();
function cancelModal() {
console.log("fechar");
setShowModal(false);
}
function openModal() {
console.log("abrir");
setShowModal(true);
}
// TODO: Review validation
function validateForm(values) {
const errors = {};
if (!values.name) {
errors.name = "Required";
}
if (!values.surnames) {
errors.surnames = "Required";
}
if (!values.email) {
errors.email = "Required";
}
if (!values.password) {
errors.password = "Required";
}
if (!values.phone) {
errors.phone = "Required";
}
if (!values.birthDate) {
errors.birthDate = "Required";
}
if (!values.gender) {
errors.gender = "Required";
}
if (!values.terms) {
errors.terms = "Required";
}
return errors;
}
async function submitForm(body, { setErrors }) {
const res = await axiosRequest({
method: "POST",
url: "/auth/register",
data: body,
});
if (res?.accessCookie) {
const accessCookie = res.accessCookie;
const refreshCookie = res.refreshCookie;
setCookie(null, "accessToken", accessCookie.token, accessCookie.options);
setCookie(
null,
"refreshToken",
refreshCookie.token,
refreshCookie.options
);
router.reload();
} else {
const errorData = res?.error?.response?.data || {};
setErrors({ [errorData.field]: errorData.error });
}
}
return (
<motion.div
className="flex flex-col min-h-full"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<HeadLogo />
<div className="grid w-full min-h-screen grid-cols-4 gap-4 px-5 rounded-background bg-purple">
<div className="col-span-3 col-start-1 text-white mt-28">
<h1 className="w-full mb-3 font-bold leading-none text-40px">
{literals.SIGNUP_TITLE}
</h1>
<p className="w-full text-base font-light">
{literals.SIGNUP_SUBTITLE}
</p>
</div>
<div className="flex-col w-full col-span-4">
{/* $FlowExpectedError[cannot-resolve-name] */}
<SignupForm
validateForm={validateForm}
submitForm={submitForm}
setCheckedGender={setCheckedGender}
checkedGender={checkedGender}
openModal={openModal}
cancelModal={cancelModal}
/>
<Modal cancelCallback={cancelModal} closeCallback={cancelModal}>
<TermsModal closeCallback={cancelModal} />
</Modal>
</div>
</div>
</motion.div>
);
}
export default Signup;
This is the component Modal:
// #flow
import { Button } from "./Button";
// $FlowExpectedError[cannot-resolve-module]
import { motion } from "framer-motion";
type Props = {
children: any,
cancelCallback?: function,
closeCallback: function,
}
export const Modal = ({
children,
cancelCallback,
closeCallback,
}: Props): React$Node => {
return (
// $FlowExpectedError
<motion.div
className="fixed inset-0 z-50 px-5 py-16 overflow-hidden bg-darkBlue bg-opacity-90 text-darkBlue"
onClick={cancelCallback || closeCallback}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{/* $FlowExpectedError */}
<motion.div
className="flex flex-col items-stretch justify-start max-h-full overflow-y-auto bg-white rounded-card"
onClick={e => e.stopPropagation()}
initial={{ y: 64, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
exit={{ y: 0, opacity: 0 }}
transition={{
type: "spring",
duration: 0.5,
bounce: 0,
}}
>
{children}
</motion.div>
</motion.div>
);
};

I don't really understand what you are trying to do. If you like to define const you can do it.
In the "component Modal:" just before return, you can define const and useState or whatever you want. But these constants will be accessible only in <Modal>.
Of course, there are components that must be on the top of the tree, for example, some initial constants and props.
P.S in nextjs you have <Link> component to your internal website routes:
<Link href={'/homepage'}>
some stuff
</Link>
You can share some screens of behaviour for future investigation.

Related

send values ​from the child component to the parent and sum the result

I'm using useFieldArray from react-hook-form to replicate my requirements component, but I need to get the return value from the requirementValueCalc function and pass it to my form component. In it I want to sum the values ​​of each inserted field. Can someone help me?
enter image description here
My form - parent component:
const ThirdPage: ForwardRefRenderFunction<
HTMLSelectElement,
ThirdPageProps
> = ({
formProps: {
register,
formState: { errors },
control,
setValue,
setFocus,
},
formData,
secondPageData,
}) => {
const {
dealId,
object,
contractPeriod,
paymentMethodId,
dueDay,
totalHours,
totalValue,
} = formData[0];
const [currentIndex, setCurrentIndex] = useState<number>(0);
const [requirements, setRequirements] = useState<RequerimentType[]>([]);
const { append, remove, fields, insert } = useFieldArray({
name: 'requirements',
control,
});
const requirementOptions = requirements?.map(
useCallback(
(requirement) => {
return {
id: requirement.id,
name: `${requirement.title} (${Math.floor(
requirement.estimate / 8
)} dias)`,
value: requirement.title,
effort: requirement.estimate,
coefficient: requirement.coefficient,
};
},
[requirements]
)
);
const handleKeyPress = (event: KeyboardEvent) => {
if (
(event.ctrlKey || event.metaKey) &&
event.key === 'ArrowUp' &&
currentIndex > 0
) {
insert(currentIndex, {});
}
if ((event.ctrlKey || event.metaKey) && event.key === 'ArrowDown') {
insert(currentIndex + 1, {});
}
if (
(event.ctrlKey || event.metaKey) &&
event.key === 'Backspace' &&
currentIndex > 0
) {
remove(currentIndex);
setFocus(`requirements.${currentIndex - 1}.title`);
}
};
useEffect(() => {
document.addEventListener('keydown', handleKeyPress);
return () => {
document.removeEventListener('keydown', handleKeyPress);
};
}, [handleKeyPress]);
useEffect(() => {
const getAllRequirements = async () => {
try {
const response = await listAllRequirements();
setRequirements(response.data.items);
} catch (err) {
console.log(err.response.data);
}
};
getAllRequirements();
append({});
}, []);
return (
<>
<div className='flex flex-col divide-y divide-zinc-700 divide-opacity-25 px-6 last:border-0 sm:px-2'>
<div className='flex flex-col'>
<div className='flex flex-row items-center justify-between gap-10'>
<Input
placeholder='Id da oportunidade vem do RD'
label='Id da oportunidade'
withoutBorder
error={errors.dealId}
{...register('dealId')}
defaultValue={dealId}
/>
<Input
placeholder='Objeto'
label='Objeto'
withoutBorder
error={errors.object}
{...register('object')}
defaultValue={object}
/>
</div>
<div className='flex flex-row items-center justify-between gap-10 pt-6'>
<Input
placeholder='Período do contrato'
label='Período do contrato'
withoutBorder
error={errors.contractPeriod}
{...register('contractPeriod')}
defaultValue={contractPeriod}
/>
<Input
placeholder='Método de pagamento'
label='Método de pagamento'
withoutBorder
error={errors.paymentMethodId}
{...register('paymentMethodId')}
defaultValue={paymentMethodId}
disabled
/>
</div>
<div className='flex items-center justify-between gap-10 pt-6'>
<Input
placeholder='Dia de vencimento da parcela'
label='Dia de vencimento da parcela'
type='number'
withoutBorder
error={errors.dueDay}
{...register('dueDay')}
defaultValue={dueDay}
/>
<div className='flex w-full flex-row items-center justify-between gap-10'>
<div className='flex w-full justify-end'>
<input
label='Total de horas'
id='totalHours'
type='number'
hidden
error={errors.totalHours}
{...register('totalHours')}
defaultValue={totalHours}
/>
</div>
<div className='flex w-full flex-col items-start gap-2'>
<input
id='totalValue'
hidden={true}
defaultValue={totalValue}
{...register('totalValue')}
/>
</div>
</div>
</div>
<hr className='mt-8 border-dark-300 pt-5' />
<div className='flex w-full justify-between'>
<h1 className='font-bold text-primary-600'>Requisitos</h1>
<Tooltip />
</div>
{fields.map((field, index) => (
<Requirement
fieldId={field.id}
errors={errors}
index={index}
register={register}
setCurrentIndex={setCurrentIndex}
requirementOptions={requirementOptions}
setValue={setValue}
currentIndex={currentIndex}
hourValue={secondPageData.hourValue}
/>
))}
<div className='flex w-full justify-end'>
<div className='flex-col'>
<div className='flex justify-end'>
<h6 className='py-2 text-white'>{`200 / horas`}</h6>
</div>
<div className='flex justify-end'>
<Title size={'bold'}>{`R$ teste`}</Title>
</div>
<div className='flex justify-end'>
<h6 className='py-2 text-white'>
<b>4 sprint</b> / ORÇADO
</h6>
</div>
<div className='flex justify-end'>
<h6 className='py-2 text-white'>
<b>{`test dias úteis`}</b> / CONTRATO
</h6>
</div>
</div>
</div>
</div>
</div>
</>
);
};
export default ThirdPage;
My requirement component - child component:
const Requirement = ({
hourValue,
fieldId,
errors,
index,
register,
setCurrentIndex,
requirementOptions,
setValue,
currentIndex,
}: SelectProps) => {
const [coefficient, setCoefficient] = useState<number>();
const [effort, setEffort] = useState<number>();
const requirementValueCalc = (effort: number, factor: number, hourValue: number) => {
const result = effort * factor * hourValue;
return result;
};
return (
<>
<div key={fieldId} className='flex flex-col gap-3'>
<div className='flex flex-row items-center justify-start gap-10'>
<div>
<Input type={'checkbox'}></Input>
</div>
<div className='w-2/6'>
<div>
{errors.requirements?.[index].title?.message && (
<span className='text-xs text-error-900'>
{errors.requirements?.[index].title?.message}
</span>
)}
</div>
<Input
id='requirementTitle'
placeholder='Título do requisito'
withoutBorder
{...register(`requirements.${index}.title`)}
onFocus={() => setCurrentIndex(index)}
className={classNames(
errors.requirements
? 'border-error-900 px-3 py-2 placeholder-error-900 focus:border-error-900 focus:ring-error-900'
: 'border-transparent px-3 py-2 placeholder-dark-300 focus:border-primary-500 focus:ring-0',
'block w-full appearance-none rounded-md border',
'bg-dark-900 text-white caret-primary-600',
'focus:outline-none sm:text-sm'
)}
/>
</div>
<div className='flex w-1/4 gap-3'>
{requirementOptions.length > 0 && (
<>
<Select
handleSetValue={(value) =>
setValue(
`requirements.${index}.typeRequirementId`,
value.id,
setCoefficient(value.coefficient),
setEffort(value.effort),
)
}
options={requirementOptions}
/>
<div className='flex ml-8 w-full gap-3'>
<Title size={'bold'}>{`R$ ${requirementValueCalc(coefficient, effort, hourValue)}`}</Title>
</div>
</>
)}
</div>
</div>
<div className='mb-5 flex flex-col gap-2'>
<div>
{errors.requirements?.[index].description?.message && (
<span className='text-xs text-error-900'>
{errors.requirements?.[index].description?.message}
</span>
)}
</div>
<textarea
id='description'
rows={2}
placeholder='Descrição do requisito'
className={classNames(
errors.requirements
? 'border-error-900 px-3 py-2 placeholder-error-900 focus:border-error-900 focus:ring-error-900'
: 'border-transparent px-3 py-2 placeholder-dark-300 focus:border-primary-500 focus:ring-0',
'block w-full appearance-none rounded-md border',
'bg-dark-900 text-white caret-primary-600',
'focus:outline-none sm:text-sm'
)}
{...register(`requirements.${index}.description`)}
/>
</div>
<hr className={currentIndex ? 'border-dark-300 pb-5' : 'invisible'} />
</div>
</>
);
};
export { Requirement };
I tried to solve it, taking the child's values ​​and sending them to the parent with a callback, but I'm not managing to keep the dynamic elements with their indexes.

React Hook Form not validating nested input field until I call onChange

I have a form which has 2 fields.
Heading
Dynamic List which contains Name & Image
I'm able to validate changes whenever Heading or Name inside dynamic list is changed.
But when I change Image, the validation is failing. It returns true instead of showing error message. I tried adding a text input to test the image. Whenever I call onChange it is working fine.
Here is my code:
import { useForm } from "react-hook-form";
import { zodResolver } from "#hookform/resolvers/zod";
import * as z from "zod";
const schema = z.object({
heading: z
.string()
.min(3, { message: "Min 3 characters" })
.max(40, { message: "Max 40 characters" }),
testimonials: z.array(
z.object({
name: z.string().min(3, { message: "Min 3 characters" }),
image: z.string().url({ message: "Invalid URL" }),
})
),
});
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
});
const [dataObj, setDataObj] = useState({
"heading": "",
"testimonials": []
});
const fetchData = () => {
// fetch list from server
// bind data
setDataObj(dataFromServer);
};
fetchData();
const listChanged = (type, value, index) => {
let list = dataObj.data.testimonials;
if (type == "name") {
list[index].name = value;
} else if (type == "image") {
list[index].image = value;
}
setDataObj({
...dataObj,
testimonials: list,
});
};
const headingChanged = (value) => {
setDataObj({
...dataObj,
heading: value,
});
};
<form className="mt-10 space-y-6" onSubmit={handleSubmit(onSubmit)}>
<div
className={`relative appearance-none block w-full px-3 py-2 border rounded-md shadow-sm placeholder-gray-400 sm:text-sm ${
errors.heading
? "border-red-400 focus:outline-none focus:ring-red-500 focus:border-red-500"
: "border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
}`}
>
<label
htmlFor="name"
className="absolute -top-2 left-2 -mt-px inline-block bg-white px-1 text-xs font-medium text-gray-400"
>
Section Heading
</label>
<div className="mt-1">
<input
type="text"
autoComplete="name"
{...register("heading", {
required: true,
})}
className="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm"
value={dataObj.data.title}
onChange={(e) => headingChanged( e.target.value)}
/>
</div>
{errors.heading && (
<small className="mt-2 text-xs text-red-500">
{errors.heading.message}
</small>
)}
</div>
<div className="mt-2 w-full py-2">
<div>
<div className="mt-2 flex justify-center mx-auto text-left">
<ul role="list" className="w-full">
{dataObj.data.testimonials.map((item, index) => (
<li key={index} className="py-3">
<div className="flex items-center space-x-4 justify-start w-full">
<div className="isolate -space-y-px rounded-md shadow-sm w-full">
<div
className={`appearance-none relative w-full px-3 py-2 border rounded-md rounded-b-none shadow-sm sm:text-sm ${
errors.testimonials?.[index]?.name
? "border-red-400 focus:outline-none focus:ring-red-500 focus:border-red-500"
: "border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
}`}
>
<label
htmlFor="name"
className="absolute -top-2 left-2 -mt-px inline-block bg-white px-1 text-xs font-medium text-gray-400"
>
Name
</label>
<input
{...register(`testimonials.${index}.name`)}
type="text"
onChange={(e) => {
listChanged(index, e.target.value, "name");
}}
value={item.name}
placeholder="New Label"
className="block w-full border-0 p-1.5 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm"
/>
<span className="invalid-feedback text-red-500 text-xs">
{errors.testimonials?.[index]?.name?.message}
</span>
</div>
<div className="relative rounded-md rounded-t-none border border-gray-300 px-3 py-2 focus-within:ring-0 focus-within:ring-indigo-600">
<label
htmlFor="name"
className="absolute -top-2 left-2 -mt-px inline-block bg-white px-1 text-xs font-medium text-gray-400"
>
Image
</label>
<input
{...register(`testimonials.${index}.image`)}
type="text"
value={item.image}
/>
<UploadImage
size={`small-round`}
index={index}
imageUrl={item.image}
imageChanged={listChanged}
/>
<span className="invalid-feedback text-red-500 text-xs">
{errors.testimonials?.[index]?.image?.message}
</span>
</div>
</div>
</div>
</li>
))}
</ul>
</div>
</div>
</div>
</form>;
How do I solve this?
I have created following minimum example on how to use image with useFieldArray to create dynamic form. This has been created in codesandbox and tested for validation only. Let me know if you stuck anywhere or want more information.
SandBoxLink :
import React from "react";
import { useForm, useFieldArray } from "react-hook-form";
import { zodResolver } from "#hookform/resolvers/zod";
import * as z from "zod";
const MAX_FILE_SIZE = 200000;
const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/png"];
const schema = z.object({
heading: z
.string()
.min(3, { message: "Min 3 characters" })
.max(40, { message: "Max 40 characters" }),
testimonials: z.array(
z.object({
name: z.string().min(3, { message: "Min 3 characters" }),
testimonial: z.string().min(3, { message: "Min 3 characters" }),
image: z
.any()
.refine(
(files) => files?.[0]?.size <= MAX_FILE_SIZE,
`Max image size is 2MB.`
)
.refine(
(files) => ACCEPTED_IMAGE_TYPES.includes(files?.[0]?.type),
"Only .jpg, .png formats are supported."
)
})
)
});
const App = () => {
const appendValues = {
Name: "",
testimonial: "",
image: []
};
const defaultValues = {
heading: "",
testimonials: [appendValues]
};
const {
register,
handleSubmit,
control,
formState: { errors }
} = useForm({
defaultValues,
resolver: zodResolver(schema)
});
const { fields, append, remove } = useFieldArray({
control,
name: "testimonials"
});
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="heading">Heading : </label>
<input {...register("heading")} id="heading" />
<p>{errors?.heading?.message}</p>
{fields.map((field, index) => (
<div
key={field.id}
style={{
border: "1px solid black",
padding: "10px"
}}
>
<label htmlFor={`testimonials.${index}.name`}>Name : </label>
<input
id={`test.${index}.name`}
{...register(`testimonials.${index}.name`)}
/>
<p>{errors.testimonials?.[index]?.name?.message}</p>
<br />
<label htmlFor={`testimonials.${index}.testimonial`}>
Testimonial :
</label>
<input
id={`test.${index}.testimonial`}
{...register(`testimonials.${index}.testimonial`)}
/>
<p>{errors.testimonials?.[index]?.testimonial?.message}</p>
<br />
<label htmlFor={`testimonials.${index}.image`}>Image :</label>
<input
id={`test.${index}.image`}
type="file"
{...register(`testimonials.${index}.image`)}
/>
<p>{errors.testimonials?.[index]?.image?.message}</p>
<br />
<button type="button" onClick={() => remove(index)}>
Remove
</button>
</div>
))}
<button type="button" onClick={() => append(appendValues)}>
append
</button>
<input type="submit" />
</form>
);
};
export default App;

React: DOMException: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local answer sdp: Called in wrong state: stable [duplicate]

This question already has answers here:
Why useEffect running twice and how to handle it well in React?
(2 answers)
Closed 3 months ago.
I am making a live stream application using peerjs in React with vite.
I know there are already many questions like this but this is specific to ReactJs. I could not find any solution related to React.
I get this error:
React: DOMException: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': Failed to set local answer sdp: Called in wrong state: stable
Edit: I found the answer and in short this is a problem that uses two other problems' solutions to fix it.
This is my webRTC.ts file.
import Peer, { DataConnection, MediaConnection } from "peerjs";
import socket from "./socket";
import { JOIN_LECTURE } from "./socketaction";
export const getUserStream: () => Promise<MediaStream> =
(): Promise<MediaStream> => {
return navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
};
export const getScreenStream: () => Promise<MediaStream> =
(): Promise<MediaStream> => {
return navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
};
export const initPeer: (peer: Peer, name: string) => void = (
peer: Peer,
name: string
) => {
// On Error
peer.on("error", (err: Error): void => {
console.error(err);
});
// On Open
peer.on("open", (id: string): void => {
console.log("[p]open", name);
console.log(id);
});
// On Connection
peer.addListener("connection", (conn: DataConnection): void => {
conn.on("data", (data: unknown): void => {
console.log(data, "data");
});
});
};
export const answerCall = (
peer: Peer,
lectureId: string,
cb: (stream: MediaStream, type: "user" | "screen") => void
): void => {
peer.addListener("open", () => {
socket.emit(JOIN_LECTURE, {
peerId: peer.id,
lectureId: lectureId,
});
// socket.emit(GET_STREAM);
});
peer.on("call", (call: MediaConnection): void => {
call.answer();
call.on("stream", (stream: MediaStream): void => {
cb(stream, call.metadata.streamType);
});
call.on("error", console.error);
call.on("close", (): void => {
console.log("call closed");
});
});
};
export const shareUser = (
peer: Peer,
stream: MediaStream,
studentId: string
) => {
if (peer && studentId) {
console.log(studentId);
const conn = peer.connect(studentId);
conn.on("open", () => {
const call: MediaConnection = peer.call(studentId, stream, {
metadata: {
streamType: "user",
},
});
call.on("error", console.error);
call.on("close", (): void => {
console.log("call closed");
});
});
conn.on("data", console.log);
conn.on("error", console.error);
conn.on("iceStateChanged", () => console.log("IceStageChanged"));
}
};
This is my main.tsx file
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { ThemeProvider } from "#mui/system";
import MuiTheme from "./mui-theme";
import { Provider } from "react-redux";
import { store } from "./redux/store";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { CookiesProvider } from "react-cookie";
import { StrictMode } from "react";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<StrictMode>
<Provider store={store}>
<CookiesProvider>
<ThemeProvider theme={MuiTheme}>
<App />
</ThemeProvider>
<ToastContainer theme="dark" position="bottom-right" />
</CookiesProvider>
</Provider>
</StrictMode>
);
This is my liveLecture.tsx file.
import {
CallEndRounded,
ChatRounded,
DoNotTouchRounded,
FullscreenRounded,
MicOffRounded,
MicRounded,
PanToolRounded,
PausePresentationRounded,
PresentToAllRounded,
SendRounded,
SpeakerNotesOffRounded,
VideocamOffRounded,
VideocamRounded,
ViewComfyOutlined,
ViewComfyRounded,
} from "#mui/icons-material";
import { Avatar, IconButton, Input, InputAdornment } from "#mui/material";
import { AnyAction, Dispatch } from "#reduxjs/toolkit";
import Peer, { DataConnection, MediaConnection } from "peerjs";
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";
import Logo from "../components/logo";
import VideoPlayer from "../components/videoplayer";
import { User } from "../redux/slices/user";
import { RootState } from "../redux/store";
import socket from "../utils/socket";
import { GET_STREAM, IS_ADMIN, JOIN_LECTURE } from "../utils/socketaction";
import {
answerCall,
getScreenStream,
getUserStream,
initPeer,
shareUser,
} from "../utils/webRTC";
interface Paused {
video: boolean;
audio: boolean;
}
function LiveLecture() {
const { lectureId } = useParams();
const user: User = useSelector((state: RootState): User => state.user);
const [userStream, setUserStream] = useState<MediaStream | undefined>();
const [screenStream, setScreenStream] = useState<MediaStream | undefined>();
const [isHandRaised, setIsHandRaised] = useState<boolean>(false);
const [isChatOpen, setIsChatOpen] = useState<boolean>(true);
const [isPresentationView, setIsPresentationView] = useState<boolean>(true);
const [paused, setPaused] = useState<Paused>({
audio: true,
video: true,
});
const [isAdmin, setIsAdmin] = useState<boolean>(false);
const [userPeer, setUserPeer] = useState<Peer>(new Peer());
const [screenPeer, setScreenPeer] = useState<Peer>(new Peer());
const [isFullScreen, setIsFullScreen] = useState(false);
const dispatch: Dispatch<AnyAction> = useDispatch();
const shareUserStream = (studentId: string) => {
if (userStream) {
shareUser(userPeer, userStream, studentId);
} else {
getUserStream().then((stream: MediaStream): void => {
setUserStream(stream);
console.log(userPeer, studentId);
shareUser(userPeer, stream, studentId);
});
}
};
useEffect((): (() => void) => {
if (userPeer && lectureId) {
initPeer(userPeer, "user");
answerCall(
userPeer,
lectureId,
(stream: MediaStream, type: "user" | "screen"): void => {
console.log("second");
if (type == "user") {
setUserStream(stream);
} else {
setScreenStream(stream);
}
}
);
}
return (): void => {};
}, [userPeer]);
useEffect((): (() => void) => {
if (screenPeer) {
initPeer(screenPeer, "screen");
}
return (): void => {};
}, [screenPeer]);
useEffect((): (() => void) => {
socket.on(IS_ADMIN, (admin: boolean) => {
setIsAdmin(admin);
if (admin) {
socket.on(GET_STREAM, (studentPeerId) => {
// call the user
studentPeerId && shareUserStream(studentPeerId);
});
}
console.log(admin);
});
return (): void => {};
}, []);
return (
<div className="flex overflow-hidden flex-col w-full h-screen">
<div className="w-full flex px-10 bg-gray-900 shadow-lg py-3">
<div className=" flex sm:gap-6 gap-4 divide-x-2 justify-center items-center text-2xl font-semibold text-white">
<div className="hidden sm:block">
<Logo />
</div>
<div className="md:pl-6 pl-4">Batch Name</div>
<div className="select-none ring-2 ring-red-500 bg-white text-red-500 font-bold uppercase px-2 rounded-lg">
Live
</div>
</div>
</div>
<div className="flex py-4 lg:justify-around lg:flex-row bg-secondary-100 grow h-[calc(100vh-16rem)] flex-col">
<div className="flex grow gap-3 flex-col justify-between items-center">
<div
className={`flex justify-center items-center grow px-4 lg:w-full`}
>
<div
className={`grid ${
!isChatOpen && "px-10"
} w-full gap-4 grid-cols-4 grid-row-4`}
>
<div
className={`${
isChatOpen ? "col-span-1" : "col-span-3 sm:col-span-1"
}`}
>
{/* secondary player */}
<VideoPlayer
stream={isPresentationView ? userStream : screenStream}
/>
</div>
<div
className={`col-span-3 px-2 grow flex justify-center items-center`}
>
{/* primary player */}
<VideoPlayer
isFullScreen={isFullScreen}
stream={isPresentationView ? screenStream : userStream}
/>
</div>
</div>
</div>
<div className="justify-center items-center">
<div className="py-2 px-6 rounded-full bg-gray-900 text-gray-500 flex gap-2 sm:gap-6 justify-center items-center">
{isAdmin ? (
<>
<IconButton
onClick={(): void => {
setPaused(
(pp: Paused): Paused => ({
...pp,
audio: !pp.audio,
})
);
}}
color="inherit"
>
{paused.audio ? (
<MicOffRounded color="inherit" />
) : (
<MicRounded color="inherit" />
)}
</IconButton>
<IconButton
onClick={(): void => {
setPaused(
(pp: Paused): Paused => ({
...pp,
video: !pp.video,
})
);
}}
color="inherit"
>
{paused.video ? (
<VideocamOffRounded color="inherit" />
) : (
<VideocamRounded color="inherit" />
)}
</IconButton>
<IconButton
onClick={() => {
if (!screenStream) {
const f: string | null = prompt("fId:");
if (screenPeer && f) {
getScreenStream().then((stream: MediaStream) => {
setScreenStream(stream);
const conn: DataConnection = screenPeer.connect(f);
conn.on("open", (): void => {
const call: MediaConnection = screenPeer.call(
f,
stream,
{
metadata: {
streamType: "screen",
},
}
);
call.on("error", console.error);
call.on("close", (): void => {
console.log("call closed");
});
});
});
}
} else {
screenStream
.getTracks()
.forEach((track: MediaStreamTrack): void =>
track.stop()
);
setScreenStream(undefined);
}
}}
color="inherit"
>
{screenStream ? (
<PausePresentationRounded color="inherit" />
) : (
<PresentToAllRounded color="inherit" />
)}
</IconButton>
<IconButton
onClick={() => {
setIsPresentationView((pipv) => !pipv);
}}
color="inherit"
>
{isPresentationView ? (
<ViewComfyOutlined color="inherit" />
) : (
<ViewComfyRounded color="inherit" />
)}
</IconButton>
</>
) : (
<IconButton color="inherit">
{isHandRaised ? (
<PanToolRounded color="inherit" />
) : (
<DoNotTouchRounded color="inherit" />
)}
</IconButton>
)}
<IconButton
color="inherit"
onClick={(): void => {
setIsChatOpen((pico: boolean): boolean => !pico);
}}
>
{isChatOpen ? (
<SpeakerNotesOffRounded color="inherit" />
) : (
<ChatRounded color="inherit" />
)}
</IconButton>
<IconButton
onClick={() => {
setIsFullScreen((pifs: boolean): boolean => !pifs);
setIsAdmin(true);
}}
color="inherit"
>
<FullscreenRounded color="inherit" />
</IconButton>
<IconButton
onClick={(): void => {
// const f: string | null = window.prompt("fId");
// getUserStream().then((stream: MediaStream): void => {
// setUserStream(stream);
// if (userPeer && f) {
// const conn: DataConnection = userPeer.connect(f);
// conn.on("open", (): void => {
// const call: MediaConnection = userPeer.call(f, stream, {
// metadata: {
// streamType: "user",
// },
// });
// call.on("error", console.error);
// call.on("close", (): void => {
// console.log("call closed");
// });
// });
// }
// });
const f: string | null = window.prompt("fId");
f && shareUserStream(f);
}}
sx={{
bgcolor: "#550000",
}}
color="error"
>
<div className="flex justify-center items-center grow">
<CallEndRounded />
</div>
</IconButton>
</div>
</div>
</div>
<div className="px-3 py-4">
<div
className={`sm:h-1/4 ${
!isChatOpen && "hidden"
} h-2/5 sm:h-1/4 lg:h-[calc(100%-5rem)] w-full lg:min-w-[20rem]`}
>
<div className="py-2 px-4 bg-primary-400 text-white shadow-lg rounded-t-lg uppercase font-semibold select-none">
live chats
</div>
<div className="flex px-4 h-full overflow-auto flex-col bg-primary-400">
<div className="flex flex-col gap-2 py-2 py-3overflow-auto">
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">This is some chat message</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
<div className="flex bg-white rounded-md gap-2 px-4 py-2 items-center">
<div>
<Avatar />
</div>
<div className="flex flex-col gap-1">
<div className="font-semibold text-sm">Name Name</div>
<div className="text-sm">
This is some chat message to test the ui responsiveness
</div>
</div>
</div>
</div>
</div>
<div className="py-2 px-4 bg-primary-400 rounded-b-md">
<Input
// value={message}
// onChange={(e) => {
// setMessage(e.target.value);
// }}
placeholder="message"
inputProps={{
className: "no-scrollbar",
}}
fullWidth={true}
className="bg-primary-400"
multiline
maxRows={3}
endAdornment={
<InputAdornment position="end">
<IconButton>
<SendRounded className="cursor-pointer" />
</IconButton>
</InputAdornment>
}
/>
</div>
</div>
</div>
</div>
</div>
);
}
export default LiveLecture;
and finally this is my server code for socket.io connection
socket.on(
JOIN_LECTURE,
({ lectureId, peerId }: { lectureId: string; peerId: string }): void => {
// verify user identity
socket.join(lectureId);
socket.to(lectureId).emit(GET_STREAM, peerId);
console.log(colors.blueBright(`[${socket.id}] joined ${lectureId}`));
// check if the connected user is admin;
// temporarily setting the first user as admin; pink;
const clientsInRoom: Set<string> | undefined =
io.sockets.adapter.rooms.get(lectureId);
const viewerCount: number = (clientsInRoom ? clientsInRoom.size : 0) - 1;
const isAdmin: boolean = !viewerCount;
socket.emit(IS_ADMIN, isAdmin);
// console.log(io.sockets.adapter.rooms);
socket.addListener(disconnect, () => {
console.log("disconnect");
// temporarily transferring admin rights to the next socket; pink;
if (isAdmin) {
const nextAdmin = clientsInRoom?.entries().next();
if (nextAdmin) {
console.log(nextAdmin, "nextAdmin");
io.sockets.to(nextAdmin.value).emit(IS_ADMIN, true);
}
}
});
}
);```
I fixed the error after some research and answer my own question.
In general this error comes from answering the same call multiple times(or so I think from this question's answers). you can see that if you are not facing this problem in ReactJs.
I found out that React calls useEffect twice in order to detect any problems with your code and warn you about them.(find more about in this question)
So, I simply removed the StrictMode wrapper from my main.tsx like:
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<Provider store={store}>
<CookiesProvider>
<ThemeProvider theme={MuiTheme}>
<App />
</ThemeProvider>
<ToastContainer theme="dark" position="bottom-right" />
</CookiesProvider>
</Provider>
);
and now my app works like it should have.
NOTE: Please see the cons of removing StrictMode in react.

problem in sending or loading data to localstorage, when I do refresh- cart data is not there to show. I am loading data from firebase

I am loading data from firebase, so when I refresh, it takes time to load it. And added data are not present in the cart that,s why I am not getting any product in the cart. But I did follow the same process to store user data, and it,s working fine, which means I am getting a registered profile in console. Can anyone suggest me a solution for getting data in the cart?
sending data:
import { motion } from "framer-motion";
import React, { useEffect, useRef, useState } from "react";
import { MdShoppingBasket } from "react-icons/md";
import { actionType } from "../context/reducer";
import { useGlobalState } from "../context/stateProvider";
import NotFound from "../img/NotFound.svg";
const RowContainer = ({ flag, data, scrollValue }) => {
const [{ cartItems }, dispatch] = useGlobalState();
const [items, setItems] = useState([]);
const rowContainer = useRef();
const addToCart = () => {
dispatch({
type: actionType.SET_CARTITEMS,
cartItems: items,
});
localStorage.setItem("cartItems", JSON.stringify(items));
};
useEffect(() => {
addToCart();
}, [items]);
useEffect(() => {
rowContainer.current.scrollLeft += scrollValue;
}, [scrollValue]);
return (
<div
ref={rowContainer}
className={`w-full my-12 flex items-center ${
flag
? "overflow-x-scroll scrollbar-none scroll-smooth"
: "overflow-x-hidden flex-wrap justify-center"
}`}
>
{data && data.length > 0 ? (
data.map((item) => {
const { id, price, imageURL, calories, title } = item;
return (
<div
key={id}
className="w-350 min-w-[300px] md:min-w-[380px] md:my-10 backdrop-blur-lg mx-1 my-2 lg:mx-2 "
>
<div className="w-full flex flex-row items-center justify-between bg-white rounded-lg drop-shadow-lg py-2 px-4 hover:bg-whiteAlpha min-h-[150px]">
<motion.img
whileHover={{ scale: 1.2 }}
src={imageURL}
alt="img"
className="w-30 max-h-40 -mt-8 duration-100 drop-shadow-2xl"
/>
<div className="flex flex-col items-end justify-end gap-4">
<motion.div
whileTap={{ scale: 0.75 }}
className="w-8 h-8 rounded-md bg-orange-500 hover:bg-orange-600 "
onClick={() => setItems([...new Set(cartItems), item])}
>
<MdShoppingBasket className="text-white text-2xl m-1" />
</motion.div>
<div className=" w-full">
<p className="text-gray-800 md:text-lg text-base text-right">
{title}
</p>
<p className="text-sm text-gray-500 text-right">
<span className="text-orange-600">$</span> {price}
</p>
</div>
</div>
</div>
</div>
);
})
) : (
<div className="w-full flex flex-col items-center justify-center">
<img src={NotFound} className="h-340" alt="" />
<p className="text-center my-2 text-xl text-red-500">No data found</p>
</div>
)}
</div>
);
};
export default RowContainer;
fetching data
export const fetchCart = () => {
const cartInfo =
localStorage.getItem("cartItems") !== "undefined"
? JSON.parse(localStorage.getItem("cartItems"))
: localStorage.clear();
return cartInfo ? cartInfo : [];
};
You are clearing your data for no reason
localStorage.getItem("cartItems") !== "undefined"
should be
localStorage.getItem("cartItems") !== undefined

is there a way to change form values on submit

i have a situation where there are two buttons that i want acting as a submit.
how can i make the value of the zip code (inside formik) be combined with the value of the option (button onclick), e.g. {"option": "adult", "zip_code": "00000"}
i've found this: https://github.com/formium/formik/issues/467#issuecomment-369833667
but it's for a class component, and doesn't show how to get the button value into formik values...
import React, { useState, useEffect } from 'react';
import { Form, Formik, useField } from 'formik';
import * as Yup from 'yup';
const FORM_INPUT_BOX = "m-2 border border-gray-300 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:border-transparent"
const FORM_INPUT_LABEL = "ml-2 text-gray-700"
const FORM_INPUT_ERROR = "break-normal relative bottom-2 left-2 text-sm text-red-500"
const FORM_DROPDOWN = "m-2 focus:ring-yellow-500 border border-gray-300 focus:border-yellow-500 h-full border-transparent bg-transparent text-gray-500 sm:text-sm rounded"
export const FORM_BUTTON = "ml-2 mr-2 py-1 border border-transparent text-medium rounded-md text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
const CustomTextInput = ({ label, ...props }) => {
const [field, meta] =useField(props); //using the hook useField, and destructuring
return(
<> {/*so we don't return any div tags - its just a react fragment */}
<label htmlFor={props.id || props.name} className={FORM_INPUT_LABEL}> {label} </label>
<input className={FORM_INPUT_BOX}{...field}{...props} />
{meta.touched && meta.error ? (
<div className={FORM_INPUT_ERROR}>{meta.error}</div>
) : null}
</>
)
}
const ButtonMaker = (props) => {
const {mainprops,userprops,adoptprops,...optionprops} = props
return(
<>
<button className={FORM_BUTTON} id={optionprops.id} onClick={()=>{optionprops.setButtonClick(true);
optionprops.setOption(optionprops.id)}}
>{optionprops.label}</button>
</>
)
};
export default function RegistrationForm(props) {
const {mainprops,userprops,...adoptprops} = props;
const [buttonClick, setButtonClick] = useState(false);
const [option, setOption] = useState('')
const allprops = {
mainprops,
userprops,
adoptprops,
buttonClick,
setButtonClick,
setOption
};
useEffect(()=>{
if (buttonClick){
alert(option)};
},[buttonClick])
// https://regex101.com/
const zipRegExp= /^([0-9]{5})$/;
//zipRegExp: only 5 digits
const zipRegExp_plus4= /^([0-9]{5})(?:[-\s]*([0-9]{4}))?$/;
return (
<Formik
initialValues={{
zip_code : ''
}}
validationSchema={Yup.object({
zip_code: Yup.string()
.min(5,'five digits needed')
.matches(zipRegExp, 'invalid zipcode')
.required('required field'),
})}
onSubmit={ (values, { setSubmitting, resetForm }) => {
setTimeout(() => {
// alert(JSON.stringify(values, null, 2));
resetForm();
setSubmitting(false);
}, 1000)
}}
>
{props => (
<Form className='bg-white rounded px-8 py-8 mt-4 mb-4'>
<div className='flex justify-center align-center'>
<section className='m-2 w-52 flex flex-col border'>
<CustomTextInput label='Enter ZIP Code' name='zip_code' placeholder='zipcode' />
</section>
</div>
<br></br>
<div className='flex justify-center align-center'>
<ButtonMaker {...allprops} groupa='pet' groupb='adopt' id='adult' label='adult'/>
<ButtonMaker {...allprops} groupa='pet' groupb='adopt' id='puppy' label='puppy'/>
</div>
</Form>
)}
</Formik>
)
}
something else i tried:
added this to each button: formkprops={props}
and this to the button maker on click function: formkprops.submitForm()
but it doesn't work.
Try the following:
import React, { useState, useEffect } from 'react';
import { Form, Formik, useField } from 'formik';
import * as Yup from 'yup';
const FORM_INPUT_BOX = "m-2 border border-gray-300 bg-white text-gray-700 placeholder-gray-400 shadow-sm text-base focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:border-transparent"
const FORM_INPUT_LABEL = "ml-2 text-gray-700"
const FORM_INPUT_ERROR = "break-normal relative bottom-2 left-2 text-sm text-red-500"
const FORM_DROPDOWN = "m-2 focus:ring-yellow-500 border border-gray-300 focus:border-yellow-500 h-full border-transparent bg-transparent text-gray-500 sm:text-sm rounded"
export const FORM_BUTTON = "ml-2 mr-2 py-1 border border-transparent text-medium rounded-md text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
const CustomTextInput = ({ label, ...props }) => {
const [field, meta] =useField(props); //using the hook useField, and destructuring
return(
<> {/*so we don't return any div tags - its just a react fragment */}
<label htmlFor={props.id || props.name} className={FORM_INPUT_LABEL}> {label} </label>
<input className={FORM_INPUT_BOX}{...field}{...props} />
{meta.touched && meta.error ? (
<div className={FORM_INPUT_ERROR}>{meta.error}</div>
) : null}
</>
)
}
const ButtonMaker = (props) => {
const {mainprops,userprops,adoptprops,...optionprops} = props
return(
<>
<button className={FORM_BUTTON} id={optionprops.id} onClick={()=>{optionprops.setButtonClick(true);
optionprops.setOption({"option": optionprops.id, "zipcode": optionprops.zip_code})}}
>{optionprops.label}</button>
</>
)
};
export default function RegistrationForm(props) {
const {mainprops,userprops,...adoptprops} = props;
const [buttonClick, setButtonClick] = useState(false);
const [option, setOption] = useState(null)
const allprops = {
mainprops,
userprops,
adoptprops,
buttonClick,
setButtonClick,
setOption
};
useEffect(()=>{
if (buttonClick){
alert(JSON.stringify(option, null, 4))};
},[buttonClick])
// https://regex101.com/
const zipRegExp= /^([0-9]{5})$/;
//zipRegExp: only 5 digits
const zipRegExp_plus4= /^([0-9]{5})(?:[-\s]*([0-9]{4}))?$/;
return (
<Formik
initialValues={{
zip_code : ''
}}
validationSchema={Yup.object({
zip_code: Yup.string()
.min(5,'five digits needed')
.matches(zipRegExp, 'invalid zipcode')
.required('required field'),
})}
onSubmit={ (values, { setSubmitting, resetForm }) => {
setTimeout(() => {
// alert(JSON.stringify(values, null, 2));
resetForm();
setSubmitting(false);
}, 1000)
}}
>
{props => (
<Form className='bg-white rounded px-8 py-8 mt-4 mb-4'>
<div className='flex justify-center align-center'>
<section className='m-2 w-52 flex flex-col border'>
<CustomTextInput label='Enter ZIP Code' name='zip_code' placeholder='zipcode' />
</section>
</div>
<br></br>
<div className='flex justify-center align-center'>
<ButtonMaker {...allprops} groupa='pet' groupb='adopt' id='adult' label='adult' zip_code={props.values.zip_code}/>
<ButtonMaker {...allprops} groupa='pet' groupb='adopt' id='puppy' label='puppy' zip_code={props.values.zip_code}/>
</div>
</Form>
)}
</Formik>
)
}
You needed to create an option property and add a zip_code propery when the actionprops' setoption function is called. In order to display this in the alert, you needed to use the JSON.stringify function to display the option using the alert function.
**Solution for Information Requested In Comments Section by Original Poster **
You can create state management for your application or use a library for this like Redux. Inside the state management, you could have an array of objects similar to the one created above. You can then give whichever component you want access to this array. You would also need a final submit for the user to indicate when they are done, or a required amount of options the user has to choose before they can move on.
References:
StackOverflow. how to alert javascript object. https://stackoverflow.com/a/64941710/8121551. (Accessed 10 October, 2021).

Categories

Resources