I'm trying to store user input in local storage and had it functioning. But because I will need to test it, I have changed my code and made a function that will work as a custom hook, that I will call in my tests also.
Now the page is rendering but I am not able to type into the input box?
When hovered over the box the mouse cursor doesn't even respond as if it isn't an input field.
I believe the problem lies in my useStateWithLocalStorage function:
import { useState, useEffect } from 'react';
const useStateWithLocalStorage = (defaultValue, key) => {
const [value, setValue] = useState(() => {
const storedValues = localStorage.getItem(key);
return storedValues !== '' ? JSON.parse(storedValues) : defaultValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
};
export default useStateWithLocalStorage;
In particular this line
return storedValues !== '' ? JSON.parse(storedValues) : defaultValue;
The value before anything is parsed into the localstorage should be name: ''
Here is my component:
import React from 'react';
import { Container, Title } from '#mantine/core';
import useStateWithLocalStorage from './Handlers';
const UserForm = () => {
const [inputValue, setInputValue] = useStateWithLocalStorage('', 'form');
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
}
function handleChange(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
setInputValue((previousValues) => ({
...previousValues,
[event.target.name]: event.target.value,
}));
}
return (
<Container>
<Title order={2}>Welcome {inputValue.name}</Title>
<form onSubmit={handleSubmit}>
<label htmlFor="name">
Name
<input
type="text"
name="name"
id="name"
placeholder="enter your name"
onChange={handleChange}
value={inputValue.name}
/>
</label>
</form>
</Container>
);
};
export default UserForm;
I hope I've explained myself well enough and haven't wasted anyone's time. I'd be thankful for any help.
Related
I have an input on the page, initially it is empty. I need to implement the following functionality: on page load, the component App fetches from localStorage a value of key appData and puts it in the input. That is, so that in the localStorage I write the value to the input and when reloading it is displayed in the input. How can i do this?
I need to use useEffect
import { useEffect, useState } from "react";
export default function App() {
const [userData, setUserData] = useState("");
useEffect(() => {
localStorage.setItem("Userdata", JSON.stringify(userData));
}, [userData]);
return (
<div>
<input value={userData} onChange={(e) => setUserData(e.target.value)}></input>
</div>
);
}
Use the change event to write to the localStorage, then use an init function in the useState hook.
import { useState } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const [ userData, setUserData ] = useState(loadUserData);
const handleUserDataUpdate = e => {
const userData = e.target.value;
setUserData(userData);
saveUserData(userData);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input id="testInput" value={ userData } onChange={ handleUserDataUpdate } />
</div>;
}
If you need an example using uncontrolled inputs, here is one using useEffect :
import { useEffect } from 'react';
const loadUserData = () => localStorage.getItem('UserData') || '';
const saveUserData = (userData) => localStorage.setItem('UserData', userData);
export default const Application = () => {
const inputRef = useRef();
useEffect(() => {
inputRef.current.value = loadUserData();
}, []); // initial load
const handleUpdateUserData = () => {
saveUserData(inputRef.current.value);
};
return <div>
<label htmlFor="testInput">Test Input</label>
<input ref={ inputRef } id="testInput" onChange={ handleUpdateUserData } />
</div>;
}
You can set a default value for the input inside state.
const [userData, setUserData] =
useState(JSON.parse(localStorage.getItem('Userdata')) || '');
So when the component mounts (after reload), the initial userData value is taken directly from the localStorage. If it's empty, the fallback value will be set ('').
Note: Make sure to add also the onChange handler to the input.
On this StackBlitz project: https://stackblitz.com/edit/node-hxolmq?file=src%2Fmain.tsx
I have the following custom control...
/src/controls/IonInputMagic2.js
import { IonInput } from "#ionic/react";
import { useEffect, useState } from "react";
const IonInputMagic2 = props => {
const { value, onChange, validityFunc, ...others } = props
var isValidValue = validityFunc(value);
const initialValue = (typeof value !== 'undefined' && isValidValue) ? value : '';
const [ currentValue, setCurrentValue ] = useState(initialValue);
useEffect(() => {
setCurrentValue(initialValue);
}, [initialValue]);
const handleChange = (e) => {
var value = e.target.value;
if (!validityFunc(value)) {
e.preventDefault();
return false;
}
setCurrentValue(value);
if (onChange) {
onChange(e);
}
};
return (
<IonInput value={currentValue} onChange={handleChange} {...others} />
);
}
export default IonInputMagic2;
where you can see I use the Ionic control: IonInput.
My problem is: I have a validityFunc(...) that decides if what the user enters is acceptable or not. As per that function, only numeric and even digits are allowed. However, the user can enter whatever character with no restrictions.
I have a similar control: IonInputMagic1 which is very similar, but it uses the HTML built-in element: <input /> instead of the Ionic control: <IonInput />. On that control the user can only enter what is expected: only numeric and even digits.
Here is the difference between those 2 controls (left: works | right: doesn't work)
Here is how I use both controls:
What I need is: To make IonInputMagic2 (which uses: IonInput) work as: IonInputMagic1 where the user can only enter numeric and even digits. This is because the IonInput uses all the styling and scripting of Ionic and I don't want to break all that by using: <input />.
Note: I have detected through the DOM that the IonInput is a wrapper of: <input />.
Any idea on how to achieve that?
If possible, please fork the StackBlitz above and post the link here.
Thanks!
This change did the trick:
Here the full code for the component:
import { IonInput } from "#ionic/react";
import { useEffect, useState } from "react";
const IonInputMagic2 = props => {
const { value, onChange, validityFunc, ...others } = props
var isValidValue = validityFunc(value);
const initialValue = (typeof value !== 'undefined' && isValidValue) ? value : '';
const [currentValue, setCurrentValue] = useState(initialValue);
useEffect(() => {
setCurrentValue(initialValue);
}, [initialValue]);
const handleChange = (e) => {
var value = e.target.value;
if (!validityFunc(value)) {
e.preventDefault();
e.target.value = currentValue;
return false;
}
setCurrentValue(value);
if (onChange) {
onChange(e);
}
};
return (
<IonInput value={currentValue} onIonInput={handleChange} {...others} />
);
}
export default IonInputMagic2;
I'm trying to validate a simple form with a single input just for practice. I also don't want the value that the user types in the input to disappear after page refresh, for that reason, I did a little bit of searching and found out about saving that data using localStorage. After trying to implement that for a while, I managed to do that, when I refresh the page, the value is still there. However, now, when I'm trying to validate the form using useForm from react-hook-form, It just doesn't work for some reason, when I try to use that same useForm logic with an input without using localStorage, It works just fine, but while trying to add localStorage functionality, then it doesn't. I hope I'm describing my problem at least okey, here's the code :
import React, {useEffect, useState } from "react";
import "./App.css"
import { useForm } from "react-hook-form";
const getForm = () => {
const storedValues = localStorage.getItem("form");
if(!storedValues) return {
name: "",
age: ""
}
return JSON.parse(storedValues);
}
function Home() {
const [values, setValues] = useState(getForm)
const {register, handleSubmit, watch} = useForm();
const handleChange = (e) => {
setValues((previousValues) => ({
...previousValues,
[e.target.name]: e.target.value,
}))
}
const onSubmit = async data => { console.log(data); };
useEffect(()=>{
localStorage.setItem("form", JSON.stringify(values))
}, [values])
return (
<div className="container">
<form onSubmit={handleSubmit(onSubmit)}>
<input value={values.name} onChange={handleChange} name="name" placeholder="name" />
<input value={values.age} onChange={handleChange} name="age" placeholder="age"/>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default Home;
This code works fine since I'm not adding useForm register to the input, but if I do that, then It gets buggy, like this :
<input value={values.name} onChange={handleChange} name="name" placeholder="name" {...register("name")} />
The latest code only works If I remove the value atrribute from the input, but I can't do that, If I do, I can't use localStorage anymore.
Looking at the documentation, you had the syntax a little off with your register function. That function takes a second argument, which is an object of props, and that is where you want to define value, name and onChange.
Like this:
<input
placeholder="name"
{...register("name", {
onChange: handleChange,
name: "name",
value: values.name
})}
/>
Here is the full code I have working on a codesandbox. That's really all I changed, expect removing the watch import.
import React, { useEffect, useState } from "react";
import "./styles.css";
import { useForm } from "react-hook-form";
const getForm = () => {
const storedValues = localStorage.getItem("form");
if (!storedValues)
return {
name: "",
age: ""
};
return JSON.parse(storedValues);
};
function Home() {
const [values, setValues] = useState(getForm);
const { register, handleSubmit } = useForm();
const handleChange = (e) => {
setValues((previousValues) => ({
...previousValues,
[e.target.name]: e.target.value
}));
};
const onSubmit = async (data) => {
console.log(data);
};
useEffect(() => {
localStorage.setItem("form", JSON.stringify(values));
}, [values]);
return (
<div className="container">
<form onSubmit={handleSubmit(onSubmit)}>
<input
placeholder="name"
{...register("name", {
onChange: handleChange,
name: "name",
value: values.name
})}
/>
<input
value={values.age}
onChange={handleChange}
name="age"
placeholder="age"
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default Home;
I have a project that is only allowed me to change this file (file below). Other files are complete, for this reason, I do not place these in here (also do not lengthen the code). My question is only about the implementation of two methods from validation in this file.
command:
An error is not displayed during the first focus on input until taken focus from it. If there is an error, it displays. and next times, during of the changing if encounter with an error, the same time displays.
I implemented the first section from the above command, how do I the second section in the same file? With 'useEffect'? How? Thanks for any helping.
input.js
import {useState} from "react";
import {validate} from './validators';
const INPUT_STATES = { /*this is not used!*/
UNTOUCHED: 'UNTOUCHED',
VALID: 'VALID',
INVALID: 'INVALID'
};
const myValidate= validate; /*validate is the my validation function*/
const Input = props => {
const [value,setValue] = useState('');
const [validate,setValidate] = useState(true);
const handleChange = (e) => {
setValue(e.target.value);
}
const handleValidate = () => {
const Validate = myValidate(value,props.validators);
setValidate(Validate);
}
return (
<div className={`form-input ${validate ? '' : 'form-input--invalid'}`} data-testid="form-input">
<label htmlFor={props.id}>{props.label}</label>
<input value={value} type={props.type} id={props.id} onChange={handleChange} onBlur={handleValidate} />
<p>{validate ? '' : props.errorText}</p>
</div>
)
};
export default Input;
I solved the above problem, by adding two If inside handleChange and handleValidate. Also, I defined another useState.
Also, I changed first parameter from myValidate, from value to e.target.value.
input.js:
import React from "react";
import {useState} from "react";
import {validate} from './validators';/*This file is cantains validation rulles.*/
const myValidate = validate; /*validate is the my validation function*/
const Input = props => {
const [value,setValue] = useState('');
const [validate,setValidate] = useState(true);
const [valid,setValid] = useState('VALID');
const handleChange = (e) => {
setValue(e.target.value);
if(valid === 'INVALID'){
handleValidate(e);
}
}
const handleValidate = (e) => {
const Validate = myValidate(e.target.value,props.validators);
setValidate(Validate);
if(!Validate){
setValid('INVALID');
}
}
return (
<div className={`form-input ${validate ? '' : 'form-input--invalid'}`} data-testid="form-input">
<label htmlFor={props.id}>{props.label}</label>
<input value={value} type={props.type} id={props.id} onChange={handleChange} onBlur={handleValidate} />
<p>{validate ? '' : props.errorText}</p>
</div>
)
};
export default Input;
Now:
An error is not displayed during the first focus on input until taken focus from it. If there is an error, it displays. and next times, during of the changing if encounter with an error, the same time displays.
I have a Form Component where it contains a state that should be updated (on input change) and it looks like this:
import { useState } from 'react';
export const Test = () => {
const [state, setState] = useState({
name: 'khaled',
age: 18
})
const handleInputChange = (e) => {
let stateCopy = state
for(let key in stateCopy) {
if(key === 'name') {
stateCopy[key] = e.target.value;
}
}
setState(stateCopy);
}
return(
<div>
<span>Name</span>
<input onChange={ handleInputChange } />
<span>{state.name}</span>
</div>
)
}
and it imported in the app component
import { Test } from '../../components/Test';
function App() {
return (
<Test />
);
}
export default App;
and whenever i try to change the name inout it not update the ui
To make the input a controlled component, both value and onChange props should be assigned.
<input value={state.name} onChange={handleInputChange} />
handleInputChange function can be improved to make sure that the state is updated immutably:
const handleInputChange = ({ target: { value } }) => {
setState(prevState => ({...prevState, name: value}));
}
This does not work because your "stateCopy" object isn't actually a copy, its the actual state object. you are setting the state to the same object which causes react to think the state didn't change at all.
instead you should copy the state like this
const handleInputChange = (e) => {
let stateCopy = {...state}
state.name = e.target.value
setState(stateCopy);
}
You should also note that unless there is a good reason for your choice of state in my opinion you should use a seperate useState for each element in the state which results in the much simpler
import { useState } from 'react';
export const Test = () => {
const [name, setName] = useState('khalad')
const [age, setAge] = useState(18)
const handleInputChange = (e) => {
setName(e.target.value)
}
return(
<div>
<span>Name</span>
<input onChange={ handleInputChange } />
<span>{state.name}</span>
</div>
)
}
simply do it like this, it will work
const handleInputChange = (e) => {
setState({...state, name: e.target.value})
}