Strange bugs with localStorage with useState react - reactjs

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)

Related

I don't understand why I am getting the value of dob as an object when in use react-datepicker

when I console log the value of dob, its an object instead of a string
initial state
const initialValues = {
registrationForm: {
dob: {
elementType: 'date',
elementConfig: {
name: 'dob',
required: true,
placeholderText: 'Date of birth'
},
value: null,
},
},
}
state*
const [values, setValues] = useState(initialValues);
updateObject
export const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties
};
};
handle change handler
const handleChange = (event, inputIdentifier, config) => {
const updatedRegistrationForm = updateObject(values.registrationForm, {
[inputIdentifier]: updatedFormElement
});
setValues({registrationForm: updatedRegistrationForm);
};
handle submit*
const handleSubmit = (event) => {
event.preventDefault()
const formData = {};
for (let formElementIdentifier in values.registrationForm) {
formData[formElementIdentifier] = values.registrationForm[formElementIdentifier].value;
}
console.log(formData)
};
for (let key in values.registrationForm) {
formElementsArray.push({
id: key,
config: values.registrationForm[key]
});
}
form
<form onSubmit={handleSubmit}>
{
formElementsArray.slice(sliceStart, sliceNumber).map(({config, id}) => {
return <Input
key={id}
elementType={config.elementType}
elementConfig={config.elementConfig}
value={config.value}
changed={(event) => handleChange(event, id)}/>
})
}
</form>
input
const Input = ({
elementType,
elementConfig,
value,
changed,
}) => {
let inputElement = null;
switch (elementType) {
case ('date'):
inputElement =
<DatePicker
id={id
className='mb-3 form-control'
{...elementConfig}
selected={value}
onChange={changed}/>
break;
}
return (
<>
{inputElement}
</>
);
};
export default Input;
I was expecting something similar to this when I console log(Stringvale)
dob: "2023-01-11T21:00:00.000Z"
but I got this(an object)
Any help will be highly appreciated.
<DatePicker
id={id
className='mb-3 form-control'
{...elementConfig}
selected={value}
onChange={changed}/>
onChange in DatePicker returns a date object. You can use any date formatters to convert the date object to required format.
Update changed method to accept the date object, do the conversions like below.
const handleChange = (event, inputIdentifier, config) => {
const updatedRegistrationForm = updateObject(values.registrationForm,
{
[inputIdentifier]: updatedFormElement
});
if(inputIdentifier=="dob") {
updatedFormElement.value=formatDate(date); //create some function to do the date formatting
} else {
updatedFormElement.value = event.target.value // for example
// logic for non date inputs
}
setValues({registrationForm: updatedRegistrationForm);
};

How to get state variable names from names(string) value in React Native?

1. I set name in statesCorelatedFields and setStatesCorelatedFields inside below codes,
how can I get state and setState variables from there? (please see below example)
2. Does my below approach right?
3. Any suggestion will be highly appreciated.
I am using react native 0.68.5.
Previously, I used class component, now I am migrating to function component.
I have a reuseable file and App file like below:
reuseable.js
// import ...
export const handleFocus = (
state,
setState,
focusStyle,
// array of state variables of corelated fields
statesCorelatedFields,
// array of setState methods of corelated fields
setStatesCorelatedFields,
// blur style if no text value
blurNoTextStyle,
) => {
const stateData = { ...state };
stateData.styleName = { ...focusStyle };
// for corelated fields: empty value and set blurNoTextStyle
if (statesCorelatedFields.length) {
let stateCorelatedFieldData;
for (i = 0; i < statesCorelatedFields.length; i++) {
stateCorelatedFieldData = { ...statesCorelatedFields[i] };
stateCorelatedFieldData.value = '';
stateCorelatedFieldData.styleName = { ...blurNoTextStyle };
setStatesCorelatedFields[i](stateCorelatedFieldData);
}
}
setState(stateData);
};
// export const handleChangeText=(state, setState, text, ...)=>{...}
// export const handleBlur=(state, setState, ...)=>{...}
// ...
App.js
// import ...
// import all methods from reuseable.js
const App = () => {
const [email, setEmail] = useState({
name: 'email',
value: '',
styleName: { ...styles.blurNoTextStyle },
error: '',
statesCorelatedFields: [],
setStatesCorelatedFields: [],
});
const [countryCode, setCountryCode] = useState({
name: 'countryCode',
value: '',
styleName: { ...styles.blurNoTextStyle },
error: '',
// I set name here; how can I get state and setState variable from here
statesCorelatedFields: ['phoneNumber'],
setStatesCorelatedFields: ['setPhoneNumber'],
});
const [phoneNumber, setPhoneNumber] = useState({
name: 'phoneNumber',
value: '',
styleName: { ...styles.blurNoTextStyle },
error: '',
statesCorelatedFields: [],
setStatesCorelatedFields: [],
});
return (
<>
{/* components */}
<TextInput
value={countryCode.value}
onChangeText={(text) => handleChangeText(countryCode, setCountryCode, text)}
onFocus={() => handleFocus(countryCode, setCountryCode, styles.focusStyle, countryCode.statesCorelatedFields, countryCode.setStatesCorelatedFields)}
onBlur={() => handleBlur(countryCode, setCountryCode)}
/>
{/* other components */}
</>
);
}
const styles = StyleSheet.create({
// styles goes here
});
export default App;
Thanks in advance.
Moves this setStatesCorelatedFields out of the loop body you are updating the state on every iteration which doesn't need. It causes to slow down your component
you can do like this:
if (statesCorelatedFields.length) {
let stateCorelatedFieldData;
for (i = 0; i < statesCorelatedFields.length; i++) {
stateCorelatedFieldData = { ...statesCorelatedFields[i] };
stateCorelatedFieldData.value = "";
stateCorelatedFieldData.styleName = { ...blurNoTextStyle };
}
setStatesCorelatedFields[i](stateCorelatedFieldData);
}

updating specific objects of array in reactjs

i want update only specific object from default _data i.e (isconformed value to 1) when user click confirm button that should object isconformed value to 1 and api put call to set the change value in data?
const default_data = {
Id: "",
productname: "",
price:"",
isconformed:0,
}
const[data,setData]=useState(default_data)
const OnChangeHandler = (key, value) => {
//console.log(value) i.e value will be 1 here
setData({ ...data, [key]: value });
};
const Confirmproduct = () => {
const res_data = {
isConformed: data.isConfirmed,
};
//API call
axios.put(`${config.APP_CONFIG}/Product/${Id}`, res_data)
.then((res) => {
setdata(res.data)
})
}
onClick={(e) => {
//when button clicked
e.preventDefault();
OnChangeHandler("isConformed", 1);
Confirmproduct()
}}
}
My expected output
{
Id: "",
productname: "",
price:"",
isconformed:1,
}
The setData function is an asynchronous hook which, when called, does not update immediately the value of data as, for example, using default_data["isconfirmed"]=value.
My recommendation is to simplify and remove the handle function as you don't need it
const default_data = {
Id: "",
productname: "",
price:"",
isconformed:0,
}
const[data,setdata]=useState(default_data)
const Confirmproduct = (key, value) => {
// setData({ ...data, [key]: value }); <- Do you really need this?
const res_data = {
isConformed: value
};
//API call
axios.put(`${config.APP_CONFIG}/Product/${Id}, res_data)
.then((res) => {
setdata(res.data)
})
** button click event**
onClick={(e) => {
e.preventDefault();
Confirmproduct("isConformed", 1);
}}

TypeError: inputs.lineItems is undefined React

const [inputs, setInputs] = useState({
taxRate: 0.00,
lineItems: [
{
id: 'initial',
name: '',
description: '',
quantity: 0,
price: 0.00,
},
]
});
function handleInvoiceChange(e) {
//setInputs(inputs => ({...inputs,[e.target.name]: e.target.value}));
setInputs({[e.target.name]: e.target.value});
}
const calcLineItemsTotal = (event) => {
return inputs.lineItems.reduce((prev, cur) => (prev + (cur.quantity * cur.price)), 0)
}
const calcTaxTotal = () => {
return calcLineItemsTotal() + (inputs.taxRate / 100)
}
and this is how i handle the change
const handleLineItemChange = (elementIndex) => (event) => {
let lineItems = inputs.lineItems.map((item, i) => {
if (elementIndex !== i) return item
return {...item, [event.target.name]: event.target.value}
})
setInputs(inputs => ({...inputs,[lineItems]:lineItems}));
//setInputs({lineItems})
}
const handleAddLineItem = (event) => {
setInputs({
lineItems: inputs.lineItems.concat(
[{ id: uuidv4(), name: '', description: '', quantity: 0, price: 0.00 }]
)
})
}
const handleRemoveLineItem = (elementIndex) => (event) => {
setInputs({
lineItems: inputs.lineItems.filter((item, i) => {
return elementIndex !== i
})
})
}
this is a react application of an invoice generator the problem occures when i add the taxrate then i get that error
Updated values to states with hooks are not merged but replaced.
Also if you are using a version of v16 or lower of react know that Synthetic event is pooled by react, i.e event object is cleared before state callback runs.
Check here for more information.
The SyntheticEvent objects are pooled. This means that the
SyntheticEvent object will be reused and all properties will be
nullified after the event handler has been called.
The correct way to update your state is as below where you use function way to update state and copy the event values you need to separate variables outside of the setInputs function call
const name = e.target.name;
const value = e.target.value;
setInputs(inputs => ({...inputs,[name]: value}));
The rest of your function updates will be as below
const handleAddLineItem = (event) => {
setInputs(input => ({
...input,
lineItems: inputs.lineItems.concat(
[{ id: uuidv4(), name: '', description: '', quantity: 0, price: 0.00 }]
)
}));
}
const handleRemoveLineItem = (elementIndex) => (event) => {
setInputs(input =>({
...input,
lineItems: inputs.lineItems.filter((item, i) => {
return elementIndex !== i
})
}));
}

React Hook useEffect has a missing dependency 'formdata'

Keep having a warning missing dependency in my form,
i tried input the formdata into the array of useEffect, but if i input the formdata, it will show Maximum update depth exceeded
Full warning : "React Hook useEffect has a missing dependency : 'formdata'. Either include it or remove the dependency array"
And my code is:
const [isRevealPassword, isRevealPasswordHandler] = useState(false);
const [formdata, formdataHandling] = useState({
email: {
value: '',
config: {
type: 'email'
}
},
password: {
value: '',
config: {
type: 'password'
}
}
});
useEffect(() => {
const newFormdata = {
...formdata
}
const newElement = {
...newFormdata['password']
}
newElement.config.type = isRevealPassword ? 'text' : 'password';
newFormdata['password'] = newElement;
formdataHandling(newFormdata);
}, [isRevealPassword]) // <--- if I input formdata here, it will looping until Maximum update depth exceeded
const togglePassword = () => {
isRevealPasswordHandler(!isRevealPassword);
}
return (
<span
onClick={() => togglePassword()}
>
{
isRevealPassword ? <FaEye /> : <FaEyeSlash />
}
</span>
)
the answer is in the comment below, all credits goes to mr #Yousaf ( will delete this message here, if mr Yousaf himself post the answer )

Resources