Form handling using Formik library in react js - reactjs

I want to handle three form in single component with suitable initialValues and Schemas, since I'm using formik and yup for form handling and validation. (is it okay to do such practice?)
const initialValuesLogin = { name: '', email: '', password: '' };
const initialValuesBookTable = {
name: '',
date: '',
time: '',
contact: '',
details: '',
};
const initialValuesRegister = {
name: '',
email: '',
password: '',
confirm_password: '',
};
const [register, setRegister] = useState(true);
`this State is used to handle the toggle between login-form and register-form`;
const [username, setUsername] = useState('');
const { values, errors, handleBlur, handleChange, handleSubmit, touched } =
useFormik({
initialValues:
props.show === 'loginForm'
? register
? initialValuesRegister
: initialValuesLogin
: initialValuesBookTable,
validationSchema:
props.show === 'loginForm'
? register
? registerSchema
: loginSchema
: bookTableSchema,
onSubmit: (values, action) => {
action.resetForm();
setUsername(values.name);
},
});
I tried to handle three different forms in single component so basically there are 3 forms as- login, register and booktable. I've created different initialValues for all of them as well as schemas, and I've used multiple ternary operators but the issue is when I submit form for login it takes initialvalues of registered form but I just want that it should take values as per selected forms like(for 'login' it takes 'initialValuesLogin' and similarly for 'register' it takes 'initialValuesRegister') Btw it works fine for booktable form !!

I'm using multiple form submissions in same components like below.
const loginValidation = Yup.object({ });
const loginFormik = useFormik({
initialValues: loginInitialValue ,
validateOnChange: false,
validateOnBlur: true,
validationSchema: loginValidation ,
onSubmit: (values) => {}
});
const registerValidation = Yup.object({ });
const registerFormik = useFormik({
initialValues: registerInitialValue ,
validateOnChange: false,
validateOnBlur: true,
validationSchema: registerValidation ,
onSubmit: (values) => {}
});
const booktableValidation = Yup.object({ });
const booktableFormik = useFormik({
initialValues: booktableInitialValue ,
validateOnChange: false,
validateOnBlur: true,
validationSchema: booktableValidation ,
onSubmit: (values) => {}
});

Related

Yup schema - `when` method is not working

Recently I am suffering with creating yup schema. Since I am doing it I see that the method when() totally doesn't work for me like it should as documentation said and other solutions I found on the internet.
When my checkbox is checked to true all the fields on the schema should be required, but they don't. As seen in my example I tried three ways, and none of them is working. Maybe there is someone who knows what am I doing wrong?
My test code is:
import "./styles.css";
import * as yup from "yup";
import { FormProvider, useForm } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import React, { useEffect } from "react";
export default function App() {
const schema = yup.object({
isRequired: yup.bool(),
firstName: yup.string().when("isRequired", (_, schema) => {
return schema.required();
}),
lastName: yup.string().when("isRequired", () => {
return yup.string().required();
}),
contact: yup.string().when("isRequired", {
is: true,
then: yup.string().required("Required")
})
});
const methods = useForm({
mode: "all",
defaultValues: {
isRequired: true,
firstName: "",
lastName: "",
contact: ""
},
resolver: yupResolver(schema),
shouldUnregister: true
});
const { register, watch } = methods;
const isRequired = watch("isRequired");
useEffect(() => {
console.log(schema.fields);
}, [isRequired, schema]);
return (
<FormProvider {...methods}>
<form autoComplete="off" noValidate>
<input type="checkbox" {...register("isRequired")} />
<input {...register("firstName")} />
<input {...register("lastName")} />
<input {...register("contact")} />
</form>
</FormProvider>
);
}
https://codesandbox.io/s/infallible-wildflower-dsyon
You third solution of how to use when was correct. The reason why it wasn't working is that in your first two when statements for firstName and lastName you always return yups required method without checking if isRequired is true.
const schema = yup.object({
isRequired: yup.bool(),
firstName: yup.string().when("isRequired", {
is: true,
then: yup.string().required()
}),
lastName: yup.string().when("isRequired", {
is: true,
then: yup.string().required()
}),
contact: yup.string().when("isRequired", {
is: true,
then: yup.string().required("Required")
})
});
And here is how it would be done when passing a function to when.
const schema = yup.object({
isRequired: yup.bool(),
firstName: yup
.string()
.when("isRequired", (isRequired, schema) =>
isRequired ? yup.string().required() : schema
),
lastName: yup
.string()
.when("isRequired", (isRequired, schema) =>
isRequired ? yup.string().required() : schema
),
contact: yup
.string()
.when("isRequired", (isRequired, schema) =>
isRequired ? yup.string().required() : schema
)
});

Why state show undefined?

I am working on form validation and I make two arrays one for check value and the second was check validation but when the page refresh and render then in my console log show undefined in my name state. whether I give him to a true value in the state. Does anyone have any idea why is this?
var validation = [{
name: true,
company: true,
email: true,
phone: true,
message: true,
}]
const [isValidate, setisValidate] = React.useState({ ...validation })
const [error, seterror] = React.useState({
name: '',
company: '',
email: '',
phone: '',
message: '',
});
const isValidFun = (param) => {
let isTrue = true;
if((param === "name" || param === "all") && (!error.name || error.name.length < 5)) {
isValidate.name = false;
isTrue = false;
} else {
isValidate.name = true;
}
setisValidate({ ...isValidate })
return isTrue;
}
const handleChange = (e) => {
error[e.target.name] = e.target.value;
seterror({ ...error });
isValidFun(e.target.name);
};
console.log(isValidate.name)
const sentMail = () => {
let isValid = isValidFun("all");
if (isValid) {
alert('Form submitted')
}
};
return (
<Input
type="text"
name="name"
placeholder="Name"
value={error.name}
onChange={handleChange}
/>
{!isValidate.name && <p className="error">This field is required</p>}
);
};
Your validation object is an array. When you initialise the state, you use the spread operator on the array inside the object, so you end up with an object with key 0 and value of the object.
var validation = [{
name: true,
company: true,
email: true,
phone: true,
message: true,
}]
console.log({...validation}]
Instead, change the validation to an object and assign it to state.
var validation = {
name: true,
company: true,
email: true,
phone: true,
message: true,
}
const [isValidate, setisValidate] = React.useState(validation);
Should be the spreader. validation variable should be like this
var validation = {
name: true,
company: true,
email: true,
phone: true,
message: true,
}

React Hooks - set state to initial state

I am using React useState to create an object in state.
This is updated to an object of data after a successful API call.
I have a form that can change this state, but I also have a cancel button.
How can i restore this state to its initial values (after API call) when cancel is clicked?
Should i create another state variable and store initial state there and then update my state based on that?
const [basePosition, setBasePosition] = useState({});
const [position, setPosition] = useState({
id: '',
title: '',
description: '',
authoredBy: '',
createdDate: '',
lastUpdatedBy: '',
lastUpdateDate: '',
sliderResponses: [],
tileResponses: [{}],
template: {}
});```
const initialState = {
id: '',
title: '',
};
const Test = () => {
const [position, setPosition] = useState(initialState);
return <>
...form
<button onClick={() => setPosition(initialState)}>Reset</button>
</>;
};
Don't create another state variable just to store initial state as it will cause another re render instead when your component is mounted then intialize your initial state object:
let initialState = null;
React.useEffect(() => {
initialState = position;
},[])
When you want to reset to initial state just use:
setPosition(initialState);
You need not to create another State. Just declare an initial state which will not be changed and assign it to the Position state when it is needed to be reset. EX:
import React,{useState} from 'react'
const YourComponent = () =>{
const initialState = {
id: '',
title: '',
description: '',
authoredBy: '',
createdDate: '',
lastUpdatedBy: '',
lastUpdateDate: '',
sliderResponses: [],
tileResponses: [{}],
template: {}
}
const [basePosition, setBasePosition] = useState({});
const [position, setPosition] = useState(initialState);
const resetState = () =>{
setPosition(initialState)
}
}
Answer to your question if you should store initial value is Yes.
That would be the easiest way to maintain your code. So put your initial value in a constant:
const INITIAL_VALUES = {
id: '',
title: '',
description: '',
authoredBy: '',
createdDate: '',
lastUpdatedBy: '',
lastUpdateDate: '',
sliderResponses: [],
tileResponses: [{}],
template: {}
}
Than every time you want to use that initial object, just spread it and all is good (spread to lose reference to constant):
const [basePosition, setBasePosition] = useState({});
const [position, setPosition] = useState({...INITIAL_VALUES});
And later when you reset:
setPosition({...INITIAL_VALUES})
import React, { useState } from 'react'
// counter
function Example3() {
const [initial, setIncrement] = useState(0)
const increment = () => {
setIncrement(initial + 1)
}
const dincrement = () => {
setIncrement(initial - 1)
}
const reset = () => {
setIncrement(0)
}
return (
<div>
<p>{initial}</p>
<button onClick={increment} >+</button>
<button onClick={dincrement} >-</button>
<button onClick={reset}>reset</button>
</div>
)
}
export default Example3;

Cannot focus on first error input in Formik

I have the following implementation of a formik component that renders a form,
I am trying to access the first error field so I can focus on it but with no avail, i will show code
const CompanyProfile = () => {
const CompanySchema = Yup.object().shape({
name: Yup.string()
.min(2, 'too short')
.required(ERROR_REQUIRED),
industry: Yup.string().notRequired(),
address: Yup.string().notRequired(),
crn: Yup.number().required(ERROR_REQUIRED),
website: Yup.string()
.notRequired()
.min(2, 'too short'),
employeesNbr: Yup.string().required(ERROR_REQUIRED),
phoneNumber: Yup.string().required(ERROR_REQUIRED),
userRole: Yup.string().required(ERROR_REQUIRED),
personCheck: Yup.boolean().required(ERROR_REQUIRED),
});
const registerCompany = async values => {
try {
const data = values;
delete data.personCheck;
await addCompany(data);
} catch (error) {
console.log(error);
}
};
const successSubmit = values => {
registerCompany(values);
};
const forSubmit = formik => {
console.log('not valid');
const { errors } = formik;
const keys = Object.keys(errors);
console.log(formik);
if (keys.length > 0) {
const selector = `[id="${keys[0]}"]`;
const errorElement = document.getElementsByName(selector);
errorElement.focus();
}
};
const formik = useFormik({
initialTouched: false,
validateOnChange: true,
validateOnBlur: true,
initialValues: {
name: '',
industry: '',
address: '',
crn: '',
website: '',
employeesNbr: '',
phoneNumber: '',
userRole: '',
personCheck: false,
},
validationSchema: CompanySchema,
onSubmit: values => {
successSubmit(values);
},
handleSubmit: formik => {
forSubmit(formik);
},
});
return (
<Skeleton pageTitle={PAGE_TITLE_COMPANY_PROFILE}>
<CompanyProfileForm formik={formik} />
</Skeleton>
);
};
export default connect(CompanyProfile);
I dont know where I am going wrong, I attached the name,value, onchange correctly in the input fields because I am able to extract the values
Thank you

NextJS: Use same component in multiple routes for multiple pages

In my NextJS app, I have a search bar component OrderSearchBar.js and I want to use it in both index.js and /purchases.js pages but with different endpoints.For example,if I click search button on the index.js page,it should post form content to /orders and on the /purchases.js, form content should post to /purchaseDetails.Is there any way to accomplish this?
OrderSearchBar.js
class OrderSearchBar extends Component{
constructor(props) {
super(props);
this.onChangeInput = this.onChangeInput.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
nature: '',
type: '',
searchBy: '',
startDate: '',
endDate: '',
keyword: ''
}
}
onChangeInput(e) {
this.setState({
[e.target.name]: e.target.value
});
}
onSubmit(e) {
e.preventDefault();
const t = {
nature: this.state.nature,
type: this.state.type,
searchBy: this.state.searchBy,
startDate: this.state.startDate,
endDate: this.state.endDate,
keyword: this.state.keyword
}
axios.post('/search', t)..then(res => console.log(res.data));
/*I can do this for single endpoint.but how do I add multiple endpoints
for use in different pages?*/
this.setState({
nature: '',
type: '',
searchBy: '',
startDate: '',
endDate: '',
keyword: ''
});
}
You can differentiate the current location in your orderSearchBar.js
by getting the pathname of window.location object.
onSubmit(e) {
e.preventDefault();
const t = {
nature: this.state.nature,
type: this.state.type,
searchBy: this.state.searchBy,
startDate: this.state.startDate,
endDate: this.state.endDate,
keyword: this.state.keyword
}
const pathName = window && window.location.pathname;
const destination = (pathName === '/purchases') ? '/purchaseDetails' : '/orders'
axios.post(destination, t)..then(res => console.log(res.data));
this.setState({
nature: '',
type: '',
searchBy: '',
startDate: '',
endDate: '',
keyword: ''
});
}
While you could use window property, this might not work if you're using Nuxt.js or other server side rendering, since the window object is not present.
Instead, I suggest you pass a prop down to your component, say:
<component :type="'isPurchaseDetails'">
or for purchases
<component :type="'isPurchases'">

Resources