What's a better way to handle input changes in react? - reactjs

Everytime i want to update some value in my state via a text input i just simply create an onChange function that accepts a event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement> as an argument. Something like this:
const [state, setState] = useState<IState>({ email: "", password: "" });
const onChange = (event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement>): void => {
const { name, value } = event.target;
setState({
...state,
[name]: string
});
}
And well it perfectly works but... i have to create this function every single time that i want to change my state via an input in other page (an onChange function in the login page, an onChange function in sign up page, forgotpassword page... etc). My question is: Is there a way to create a generic onChange that accepts any state and update it?
this is my first post on stack overflow, sorry if i dont follow the guidelines at all.

If the function is short and only used once, then one option you could consider would be to define it inline.
<input
name="name"
value={state.name}
onChange={e => { setState({ ...state, name: e.target.value }); }}
/>
You can also consider separating out states, as React recommends - there's no need to put everything into a single state variable (unless there are a lot of inputs in a form, in which case a single state object with a generic updater can make things more DRY).
const [name, setName] = useState(''); // No need for type annotations anymore
const [password, setPassword] = useState('');
// ...
<input
name="name"
value={name}
onChange={e => { setName(e.target.value); }}
/>
If you want a generic state updater that you don't have to redefine every time to have another state variable, you can create a higher order function that you pass in a state setter, that returns the change handler - then import this function wherever it's needed.
const makeChangeHandler = (stateSetter: React.Dispatch<React.SetStateAction<string>>) => {
return (event: React.ChangeEvent<HTMLInputElement | HtmlTextAreaElement>) => {
const { name, value } = event.target;
stateSetter({
...state,
[name]: value
});
};
};
<input
name="name"
value={name}
onChange={makeChangeHandler(setState)}
/>
Refactoring that function to deal with separate states instead of having a single state object is trivial, if desired, just do
stateSetter(value);

Related

Adding validation to simple React + Typescript application

I have kind of an easy problem, but I'm stuck because I do not know TypeScript well (and I need to get to know it very quickly).
I have to add simple validation on submit which will check if a value is not empty.
I have the simplest React form:
type FormValues = {
title: string;
};
function App() {
const [values, handleChange] = useFormState<FormValues>({
title: ""
});
const handleSubmit = (evt: FormEvent) => {
evt.preventDefault();
console.log("SUBMITTED");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
id="title"
name="title"
onChange={handleChange}
type="text"
value={values.title}
/>
<button type="submit">submit</button>
</form>
</div>
);
}
Custom hook to handle form:
import { useCallback, useReducer } from "react";
type InputChangeEvent = {
target: {
name: string;
value: string;
};
};
function useFormState<T extends Record<string, string>>(initialValue: T) {
const [state, dispatch] = useReducer(
(prevState: T, { name, value }: InputChangeEvent["target"]) => ({
...prevState,
[name]: value
}),
initialValue
);
const handleDispatch = useCallback(
(evt: InputChangeEvent) => {
dispatch({
name: evt.target.name,
value: evt.target.value
});
},
[dispatch]
);
return [state, handleDispatch] as const;
}
export default useFormState;
Now I'd like to add simple validation on submit and (because of TS) I have no idea how. I've thought about three options:
Put the validation logic to the handleSubmit method.
Put the validation logic inside custom useFormState hook.
Create another custom hook just to manage validation only.
I tried to handle the first (and I think the worst) solution in this CodeSandbox example, but as I said the TS types are stronger than me and the code does not work.
Would anyone be so kind and help me with both cases (pick the most correct solution and then, run the code with TS)?
A common approach would be to just store the form error value in a React useState variable and render it as necessary.
const [error, setError] = React.useState('');
// ...
const handleSubmit = (evt: FormEvent) => {
evt.preventDefault();
// This might be the wrong way to check the input value
// I haven't worked directly with form submit events in a hot minute
if (!evt.target.value) {
setError('Title is required');
}
};
// ...
<input
id="title"
name="title"
onChange={handleChange}
type="text"
value={values.title}
aria-describedby="title-error"
required
aria-invalid={!!error}
/>
{error && <strong id="title-error" role="alert">{error}</strong>}
Notice that the aria-describedby, required, aria-invalid and role attributes are important to enforce semantic relationships between the input and its error, announce the error to screen readers when it appears, and designate the input as "required".
If you had multiple inputs in the form, you can make your error value into an object that can store a separate error for each field of your form:
const [errors, setErrors] = React.useState({});
// ...
setErrors(oldErrors => ({...oldErrors, title: "Title is required"}));
// ...
{errors.title && <strong id="title-error" role="alert">{errors.title}</strong>}
Another common pattern is to clear an input error when it is modified or "touched" to allow the form to be resubmitted:
onChange={(e) => {
setError(''); // or setErrors(({title: _, ...restErrors}) => restErrors);
handleChange(e);
}}
Note that all of this error handling logic can be rolled into your custom hooks for form/input handling in general, but does not have to be.

React Native: Reset children states from grandfather?

This is a tricky question. Lets say I have a Component <GrandFather /> with a <Form /> Component inside. The <Form /> has Multiple <Input /> Components. The <Input /> has an internal useState. At the <GrandFather />, there are some functions like loadForm(loading some fields) and unloadForm(removes these fields). On the unloadForm I wanna reset the internal State of the Inputs. The only solution I found was to have a key on the <Form /> and increment it on unload, so thats forces the rest. Is there a better good way to do this without change the logic? P.S I 'm using Typescript.
function GrandFather (props: Props) {
const loadForm = () => // load some fields to the formData
const unloadForm = () => // unload these fields to the formData
return <Form formData={formData}/>
}
function Form (formData: FormData) {
return (
<>
<Input /> // with some props
<Input /> // with some props
<Input /> // with some props
</>
)
}
function Input (props: Props) {
const [state, setState] = useState(false);
// the state here is being used for styling and animations, at
// somepoint it will became true
return <TextInput {...props}/>
}
any way here to reset this state to all the inputs on the function unloadForm?
I see two different methods of achieving this.
Method #1
As an example, I'll use a simple login form.
So you could define the following state variables on GrandParent:
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
Then, pass all username, setUsername, password, and setPassword as props to Form and then on to the Input components.
Inside of the Input component, you could turn the inputs into controlled inputs by adding the following:
<input type="text" value={username} onChange={setUsername} />
If you ever need to clear the inputs, you could call right directly from GrandParent or Form (or anywhere where you have access to the setters) the following:
setUsername('');
setPassword('');
Method #2 (hacky)
You could define, on GrandParent, a state variable that, as the above method, you would pass both the variable and setter as props to Form and then to each Input:
const [clear, setClear] = useState(false);
Then, inside the Input component, assuming you have a value state variable on it (with the equivalent setter setValue), you could set up a listener for a change on the state variable clear:
useEffect(() => {
if (clear === true) {
setValue('');
}
}, [clear]);
Then, whenever you wanted to clear all the input values, you could call, from anywhere:
setClear(true);
setTimeout(() => { // the setTimeout might not be required
setClear(false);
}, 1);

Can re-rendering of all inputs be avoided when passing a whole object?

I have quite a few different interfaces with many properties like
interface IEntity1 {
a: string
b: number
c: string
....
}
When I want to edit an object (i.e., create a modified copy), I can do
function Editor(props: {entity: IEntity1, setEntity(e: IEntity1) => void}) {
const {entity, setEntity} = props;
const [a, setA] = useState(entity.a);
const [b, setB] = useState(entity.a);
....
function handleOk() {
setEntity({a, b, c, ...});
}
return <div>
<input value={a} onChange={e => setA(e.target.value}/>
<input value={''+b} onChange={e => setB(+e.target.value}/>
<input value={c} onChange={e => setC(e.target.value}/>
...
<button onClick={handleOk}>OK</button>
</div>;
}
This works fine, but it's pretty repetitive. So I created a "universal" component, which can handle number, string and other inputs. Using it, I can handle all fields in a loop like
{fieldNames.map(f => <Edit value={entity[f]}
onChange={newValue => setEntity({...entity}, {[f]: newValue})}/>)}
I can generate all the needed metadata (above for simplicity just fieldNames: string[]) easily. I can do even better with
{fieldsMetadata.map(f => <Edit2 entity={entity} field={f}/>)}
where Edit2 is a "smarter" component. What bothers me is that all my edits get re-rendered on any change. This may be a premature optimization, but given that I want to use this component many time on many pages, it may be a valid concern.
So I'm asking whether it's possible to write such a component without it re-rendering every time or whether there's something performance-relevant what I'm missing?
The problem in with your component re-rendering is because on passing the inline arrow function which is created again on every render
Once you avoid it you can implement each Edit component with React.memo or PureComponent depending on whether you implement it as a functional component or class
const onChange= useCallback((name, value) => {
setEntity(prev => ({...prev, [name]: value}));
}, []);
...
{fieldNames.map(f => <Edit name={f} value={entity[f]}
onChange={onChange}/>)}
and Edit component would be like
const Edit = (props) => {
const {value, name, setEntity} = props;
const handleChange = (e) => {
const newValue = e.target.value;
onChange(name, newValue);
}
return <input value={value} name={name} onChange={handleChange} />
}
export default React.memo(Edit)
P.S Make sure you are merging the state correctly with setEntity

in react hook how can I empty my states and show the empty inputs in same time?

in react hook how can I empty my states and show the empty inputs in same time?
My problem is, when I reset state my Input remain full.
const [state, setState] = useState([initial])
handleInputChange(event) {
setState({
event.target.value
});
}
const resetState = () => {
setState([initial])
}
export default function newState () {
return(
<input onChange={handleInputChange()} />
<button onClick={resetState}/>
)
};
What (I think) you mean to to is this:
export default function Foo({ initial }) {
const [value, setValue] = React.useState(initial)
return(
<>
<input onChange={(event) => setValue(event.target.value)} value={value} />
<button onClick={() => setValue(initial)}>Reset</button>
</>
)
};
You have to call useState inside your components.
First problem: You have initialized state to an array, reset it to an array, but update it to an object.
Always keep your data types consistent, in this case neither seem appropriate. I would suggest just using a string value.
const [state, setState] = useState(initial)
handleInputChange(event) {
setState(event.target.value);
}
const resetState = () => {
setState(initial)
}
Remember - The useState updater is not the same as the class based setState. setState merges the previous and new state values. useState replaces. So if your state is not an object the update call setState({ newState }) is not correct anymore.
Second problem: <input onChange={handleInputChange()} /> is not right. What this code does is assign onChange the value returned from calling handleInputChange. What you actually want is to assign onChange the function itself.
// no ()
<input onChange={handleInputChange} />
Third Problem: Your state hook doesn't appear to be inside the functional component and it doesn't look like its set up for use as a custom hook (I could be wrong). Move them into the component function.

ReactJS How to handle this that refers to DOM

In vanilla js if we want to add a listener to a dom we can do like this
<div onclick="myFunction(this)">...</div>
but wen i do it inside react component this is refers to component class itself. how to handle something like this?
You should pass event in your function, and then you access DOM element with e.target.
For example if you want to handle input change, you can do something like this:
const [values, setValues] = useState({
username: '',
});
const handleChange = event => {
event.persist();
setValues(values => ({
...values,
[event.target.id]: event.target.value
}));
};
event.target.id is the id of DOM element and event.target.value is the value of the same element, which in this case is input.
<input
id="username"
type="text"
placeholder="Enter username"
onChange={handleChange}
value={values.username}
required
></input>
Also
Note:
If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.
In react u should use onClick (with capital C) and pass the event to your function
<div onClick={(e) => {myfunction(e)}}>...</div>
Then in your function, use event.target to get the clicked tag.
const myfunction = (event) => {
let id = event.target.id;
}
In your context, this refers to the DOM target element, so you'd define your function as:
function myFunction(target) { /* Code here */ }
But in react, when you define an input:
<input onClick={myFunction} />
Your function is given an event not a target, so your function is defined as:
function myFunction(event) { console.log(event.target) }

Resources