Formik form won't clear out - reactjs

I was trying to populate the initialValue of my formik form with its hoc data onClick.
Clearing the form back to its first initialValue (initValues) when the reForm() is used does'nt work and it always goes back to the value from the prop.
parent:
const [contactData, setContactData] = useState();
.
..
....
<First userData={contactData}/>
<Second userData={setContactData}/>
child:
function FirstComponent({userData}) {
const [isUpdate, setUpdate] = useState(false);
const initValues = {
id:'',
email: '',
name: '',
contact: ''
};
useEffect(() => {
// setContactData(userData)
userData!==undefined ? setUpdate(true) : setUpdate(false);
console.log("from form: ", userData)
}, [userData]);
const formik = useFormik({
enableReinitialize: true,
validationSchema: FormSchema,
initialValues: userData || initValues,
onSubmit: (values) => {
// same shape as initial values
console.log("the Data ", userData)
console.log("onsubmit ", values);
}
})
const resForm = () => {
formik.initialValues = {initValues};
console.log("prop ", userData)
console.log("formik values ",formik.initialValues)
formik.resetForm();
}
Replacing the initialValues with initValues in formik.resetForm() seems to work, but it makes the form reinitialize back with the userData prop - function FormComponent({userData})

partially fixed..
just modified the formik.resetForm() in resForm() to
formik.resetForm({
values:initValues
});
but the prop (userData) from parent still persists when console logged in resForm()

Related

In React app, converting class components to functional components, how to re-work this setState instance?

In a React app, I have a class component that acts as a form, and each different element of the form calls the same reusable function to set state in order to save data to state until the form is finished, then a callback to pass the data around elsewhere, and when that form is finished, it writes the data in state to a MongoDB. In calling the function, I pass a parameter into it that determines which property of state is set.
I'm trying to convert this component to a functional component, but can't come up with as elegant of a way to handle this using useState.
The function:
handleChange = (e, t, l) => {
if (l) {
var lang = this.state[l]
lang[t] = e.target.value
this.setState({[l] : lang}, () => { this.setData() })
} else {
this.setState({[t]:e.target.value}, () => { this.setData() })
}
}
And the implementation:
onChange={(e) => this.handleChange(e, 'eng')}
onChange={(e) => this.handleChange(e, 'span')}
onChange={(e) => this.handleChange(e, 'type')}
Those occur on multiple inputs and there are a few other places, those are just 3 to give example.
I'd guess the best way to handle this is to use a switch, and each case is setEng, setSpan, setType, but I wanted to see if there were any other ways to replicate this that people know of? This seems like a bit of a shortcoming of useState that setState handled really nicely.
Edit:
As requested, state as it was in class component:
this.state = {
id: this.props.count,
collapsed: false,
type: '',
answer: null,
english: {
title: '',
options: [],
},
spanish: {
title: '',
options: [],
}
}
And in functional component:
const [id, setId] = useState(props.count)
const [collapsed, setCollapsed] = useState(false)
const [type, setType] = useState('')
const [answer, setAnswer] = useState(null)
const [english, setEnglish] = useState({ title: '', options: [] })
const [spanish, setSpanish] = useState({ title: '', options: [] })

setting up custom error validation in react

I am trying to create some custom error validation in React
I have a values obj in state and an error obj in state that share the same keys
const [values, setValues] = useState({
name: "",
age: "",
city: ""
});
const [err, setErr] = useState({
name: "",
age: "",
city: ""
});
i have a very simple handle change and an onSubmit which i want to run my custom validator function inside
const handleChange = (e) => {
setValues({
...values,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e) => {
e.preventDefault();
validateForms();
};
in my validateForms function my theory is since both my pieces of state share the same keys I am trying to see if any of those values === '' if yes match is the same key in the err obj and set that respective value to the error and then do other stuff in JSX
const validateForms = () => {
for (const value in values) {
if (values[value] === "") {
setErr({
...err,
value: `${value} is a required field`
});
}
}
};
I definitely feel like I'm not using setErr properly here. Any help would be lovely.
link to sandbox: https://codesandbox.io/s/trusting-bartik-6cbdb?file=/src/App.js:467-680
You have two issues. First, your error object key needs to be [value] rather than the string value. Second, you're going to want to use a callback function in your state setter so that you're not spreading an old version of the error object:
const validateForms = () => {
for (const value in values) {
if (values[value] === "") {
setErr(err => ({
...err,
[value]: `${value} is a required field`
}));
}
}
};
A more intuitive way to set errors might be to accumulate them all and just set the error state once:
const validateForms = () => {
const errors = {};
for (const value in values) {
errors[value] = values[value] === "" ? `${value} is a required field` : "";
}
setErr(errors);
};

Strange bugs with localStorage with useState react

I tried to save values of the input with localStorage and i have a strange bugs, it doens't load data from localStorage.
1- I set the data of multiple input with setItem in useEffect in the InputFile.js
useEffect(() => {
let validateInputs = {
fullNameValidate: enteredFullNameIsValid,
phoneValidate: enteredPhoneIsValid,
};
setValidation(validateInputs);
const allData = { fullNameEntered, phoneEntered };
localStorage.setItem("data", JSON.stringify(allData));
}, [
enteredFullNameIsValid,
enteredPhoneIsValid,
setValidation,
fullNameEntered,
phoneEntered,
]);
2 - i update the value of the handler in the custom hooks (use-input.js) :
const [enteredValue, setEnteredValue] = useState({
enterValidate: "",
});
const valueChangeHandler = (event) => {
setEnteredValue({
...enteredValue,
enterValidate: event.target.value,
});
};
3- I tried to take the values saved with (in custom hooks, use-input.js):
useEffect(() => {
const saved = localStorage.getItem("data");
const { fullNameEntered, phoneEntered } = JSON.parse(saved);
setEnteredValue((prevState) => {
return { ...prevState, fullNameEntered, phoneEntered };
});
}, []);
But it doesn't works!
UPDATE:
here there are the 2 complete files:
1- custom hooks for the inputs
const useInput = (validateValue) => {
const [enteredValue, setEnteredValue] = useState({
enterValidate: "",
});
const [isTouched, setIsTouched] = useState(false);
const [clickClasses, setClickClasses] = useState(false);
const valueIsValid = validateValue(enteredValue.enterValidate);
const hasError = !valueIsValid && isTouched;
const valueChangeHandler = (event) => {
setEnteredValue({
...enteredValue,
enterValidate: event.target.value,
});
};
const inputBlurHandler = () => {
setIsTouched(true);
setClickClasses(false);
};
const inputClickHandler = () => {
setClickClasses(!clickClasses);
};
const reset = () => {
setEnteredValue("");
setIsTouched(false);
};
////TAKE STORED DATA////// BUT IT DOESN'T WORK
useEffect(() => {
const saved = localStorage.getItem("data");
const { fullNameEntered, phoneEntered } = JSON.parse(saved);
setEnteredValue((prevState) => {
return { ...prevState, fullNameEntered, phoneEntered };
});
}, []);
console.log(enteredValue);
return {
value: enteredValue.enterValidate,
isValid: valueIsValid,
hasError,
valueChangeHandler,
inputBlurHandler,
inputClickHandler,
click: clickClasses,
reset,
};
};
export default useInput;
2- Input files
import React, { useEffect } from "react";
import useInput from "../../../../../hooks/use-input";
import validateInput from "../../../../../utils/validateInput";
import {
WrapperNamePhone,
LabelNamePhone,
SpanInputDescription,
InputStyle,
} from "../ContactFormInput.style";
export default function FullNamePhoneInput({ setValidation }) {
//FullName Input
const {
value: fullNameEntered,
isValid: enteredFullNameIsValid,
hasError: fullNameHasError,
valueChangeHandler: fullNameChangeHandler,
inputBlurHandler: fullNameBlurHandler,
inputClickHandler: fullNameClickHandler,
click: fullNameClickClasses,
} = useInput((value) => {
const inputValidFullName = {
value: value,
maxLength: 20,
whiteSpace: true,
allowNumber: false,
allowStrings: true,
};
return validateInput(inputValidFullName);
});
//Phone numbers input
const {
value: phoneEntered,
isValid: enteredPhoneIsValid,
hasError: phoneHasError,
valueChangeHandler: phoneChangeHandler,
inputBlurHandler: phoneBlurHandler,
} = useInput((value) => {
const inputValidPhone = {
value: value,
maxLength: 15,
whiteSpace: true,
allowNumber: true,
allowStrings: false,
};
return validateInput(inputValidPhone);
});
useEffect(() => {
let validateInputs = {
fullNameValidate: enteredFullNameIsValid,
phoneValidate: enteredPhoneIsValid,
};
setValidation(validateInputs);
//STORE DATA////
const allData = { fullNameEntered, phoneEntered };
localStorage.setItem("data", JSON.stringify(allData));
}, [
enteredFullNameIsValid,
enteredPhoneIsValid,
setValidation,
fullNameEntered,
phoneEntered,
]);
//FUll NAME
const borderColorFullName = fullNameHasError ? `rgb(245, 2, 2)` : `#d5d9dc`;
const clickedColor = fullNameClickClasses ? "#2696e8" : "#a4aeb4";
//PHONE NUMBERS
const borderColorPhone = phoneHasError ? `rgb(245, 2, 2)` : `#d5d9dc`;
return (
<WrapperNamePhone>
<LabelNamePhone htmlFor="full-name">
<SpanInputDescription clickedColor={clickedColor}>
Full name
</SpanInputDescription>
<InputStyle
type="text"
name="full-name"
id="full-name"
borderColor={borderColorFullName}
value={fullNameEntered}
onChange={fullNameChangeHandler}
onBlur={fullNameBlurHandler}
onClick={fullNameClickHandler}
/>
{fullNameHasError && <p> - Enter a valid Full Name</p>}
</LabelNamePhone>
<LabelNamePhone htmlFor="phoneNumber">
<InputStyle
placeholder="Enter a valid phone number"
type="text"
name="phoneNumber"
borderColor={borderColorPhone}
value={phoneEntered}
onChange={phoneChangeHandler}
onBlur={phoneBlurHandler}
/>
{phoneHasError && <p> - Enter a valid Phone Number</p>}
</LabelNamePhone>
</WrapperNamePhone>
);
}
///////////////////////////
//////////////////////////
Final Update, i don't like this solution but it works!
I've done like this:
-1 I deleted the setKeys from the Input files.
-2 I update the setKeys dinamically in the use-input hooks:
-3 then with useState i update the getItem!
const [enteredValue, setEnteredValue] = useState(() => {
return {
validateInput: "",
fullName: JSON.parse(localStorage.getItem("fullName")) || "",
phoneNumber: JSON.parse(localStorage.getItem("phoneNumber")) || "",
email: JSON.parse(localStorage.getItem("email")) || "",
country: JSON.parse(localStorage.getItem("country")) || "",
From: JSON.parse(localStorage.getItem("From")) || "",
To: JSON.parse(localStorage.getItem("To")) || "",
};
});
const valueChangeHandler = (event) => {
setEnteredValue({
[event.target.name]: event.target.value,
validateInput: event.target.value,
});
localStorage.setItem(event.target.name, JSON.stringify(event.target.value));
};
*/ Other code for validation, useless in this example */
return {
value: enteredValue.validateInput,
valueName: enteredValue.fullName,
valuePhone: enteredValue.phoneNumber,
valueEmail: enteredValue.email,
valueCountry: enteredValue.country,
valueFrom: enteredValue.From,
valueTo: enteredValue.To,
isValid: valueIsValid,
hasError,
valueChangeHandler,
inputBlurHandler,
inputClickHandler,
click: clickClasses,
reset,
};
I m not sure i have completely understood your problem, however instead of doing all that in useState you could use an effect and set its value based on any dependency or just on component mount like so:
const [enteredValue, setEnteredValue] = useState({});
useEffect(()=>{
const saved = localStorage.getItem("data");
const { fullNameEntered, phoneEntered } = JSON.parse(saved);
setEnteredValue({
...enteredValue,
fullname:fullNameEntered,
phone:phoneEntered
})
},[])
you can change to add some conditions to ensure its not null before being set etc. but this approach should work in general instead of using a callback in useState.
You can then use the values in the component via the state i.e enteredValue?.fullname etc. (?. is optional chaining to prevent undefined errors)

update the value of nested objects using useState

For form validation, I am using useState hooks. Below is the code
const [address, setAddress] = useState(shippingAddress.address)
const [city, setCity] = useState(shippingAddress.city)
const [postalCode, setPostalCode] = useState(shippingAddress.postalCode)
const [country, setCountry] = useState(shippingAddress.country)
const [formState, setFormState] = useState({
formErrors: { address: '', city: '', postalCode: '', country: '' },
addressValid: false,
cityValid: false,
postalCodeValid: false,
countryValid: false,
formValid: false,
})
const dispatch = useDispatch()
useEffect(() => {
const prevState = { ...formState }
if (address === '') {
prevState.formErrors.address = 'address not valid'
prevState.addressValid = false
setFormState(prevState)
}
code continues...
I am having trouble to update the formErrors.address value. It shows errors in the browser as:
TypeError: Cannot set property 'address' of undefined
useEffect(() => {
const prevState = { ...formState }
if (address === '') {
prevState.formErrors.address = 'address not valid'
prevState.addressValid = false
setFormState(prevState)
}
As mentioned above, we'd need to see the code where you are setting your assignments, but in the useEffect you can clean up some of the confusion by using the prevState (which is actually current state) baked into useState. Then, you really only care when the address changes, rather than having this run every time.
useEffect(() => {
if (address === '') {
setFormState(prevState =>( {
...prevState,
formErrors.address = 'address not valid',
addressValid: false
}))
},[address])

useFieldArray react-hook-form

There is something that I am blocking and it makes no sense at all. For you who are familiar with react-hook-form, I am attempting to create a dynamic field array that renders according to the state object. The thing is, it renders on the first render but not on the second render.
Example:
let subK = [{ name: '' }]
if (kategories[kategori] !== undefined) {
//subK = kategories[kategori].subKategori.map(x => ({ name: JSON.stringify(x) }))
subK = kategories[kategori].subKategori.map(x => ({ name: (x) }))
}
console.log(subK) // it logs[{name: 'kat1'},{name: 'kat2'}]
//defines the form
const { register, control, handleSubmit } = useForm({
defaultValues: {
subKategori: subK
}
});
does not render subK.
But if I do
let subK = [{ name: '' }]
if (kategories[kategori] !== undefined) {
//subK = kategories[kategori].subKategori.map(x => ({ name: JSON.stringify(x) }))
subK = kategories[kategori].subKategori.map(x => ({ name: (x) }))
}
console.log(subK)
//defines the form
const { register, control, handleSubmit } = useForm({
defaultValues: {
subKategori: [{name: 'kat1'},{name: 'kat2'}]
}
});
it renders as it is supposed too.
What am I doing wrong?
There is a minor issue in the structure of subKategori at line number 8. It seems an array is in stringified form. But for map, you need an array. Converting it as following at line number 8 shall work :
....
kat1: {
subKategori: ["kat1", "kat2"]
},
...
Here is the updated sandbox link

Resources