Reputation: 876
TL;DR
Is there any way to dynamically update the value for input fields with one event handler function like we could do with stateful component.
I am working on a login form with 2 fields. Email and Password. When i am using 2 useState representing the two field, then when i update the state with handleChange both state get updated. Which is not the intention.
const [email, setEmail] = useState()
const [password, setPassword] = useState()
const handleChange = e => {
const {value} = e.target
setEmail(value)
setPassword(value)
}
I don't want to use multiple event handler for handling each input field. I tried this
const [state , setState] = useState({
email : "",
password : ""
})
const handleChange = e => {
const {name , value} = e.target
setState({
[name] : value
})
}
But this updates one property at a time. And the other property value get lost. So is there any way that i can update all my input fields with one event handler function like we could do with stateful component.
this.state = {
email : "",
password : ""
}
const handleChange = e => {
const {name , value} = e.target
this.setState({
[name] : value
})
}
Upvotes: 9
Views: 27205
Reputation: 4011
You could create a custom hook like so:
import { useState } from 'react';
export const useForm = (initialValues) => {
const [values, setValues] = useState(initialValues);
return {
values,
handleChange: (e) => {
setValues({
...values,
[e.target.name]: e.target.value,
});
},
reset: () => setValues(initialValues),
};
};
Then use it in any form (for example):
export default function SignInForm() {
const { values, handleChange, reset } = useForm({ username: '', password: '' });
const handleSubmit = e => {
e.preventDefault();
reset();
};
return (
<form onSubmit={handleSubmit}>
<input
type='text'
name='username'
placeholder='Enter your username...'
onChange={handleChange}
value={values.username}
/>
<input
type='password'
name='password'
placeholder='Enter your password...'
onChange={handleChange}
value={values.password}
/>
<button type='submit'>
Sign In
</button>
</form>
);
}
Upvotes: 9
Reputation: 2017
CREATE A DICTIONARY OF ALL INPUTS
You could save a reference of the state
and setState
for each input in an object (serving as a dictionary), using the name
of the input.
Below example also works for the checkbox
inputs:
import React, { useState } from "react";
const setStateOfInput = 1;
function Form() {
const inputs = {}; // Dictionary used to save a reference [state, setState] for each input (e.g. inputs.todo)
const [todoInput, setTodoInput] = inputs.todo = useState("");
const [doneInput, setDoneInput] = inputs.done = useState(false);
function handleInput(e) {
let { name, value, type } = e.target;
// If input is a checkbox
if (type === "checkbox") value = e.target.checked;
inputs[name][setStateOfInput](value);
}
return (
<div>
<form>
<label>To Do</label>
<input name="todo" value={todoInput} onChange={handleInput} />
<label>Done</label>
<input name="done" type="checkbox" checked={doneInput} onChange={handleInput}/>
<button type="button">Add</button>
</form>
</div>
);
}
export default Form;
CREATE A CUSTOM HOOK
In addition, the above could be refactored to create a custom hook that can be used for forms/inputs in other components:
function useInputState(initialValue, name, inputsDict) {
if (inputsDict[name]) throw new Error(`input "${name}" already exists.`);
inputsDict[name] = useState(initialValue);
return inputsDict[name]
}
|
const inputs = {}; // Dictionary used by `useInputState`
const [todoInput, setTodoInput] = useInputState("", "todo", inputs);
Upvotes: 0
Reputation: 31
Try this:
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const handleChange = (name, e) => {
switch (name) {
case 'email':
setEmail(e);
break;
case 'password':
setPassword(e);
break;
default:
break;
}
};
Your inputs:
<input
type="text"
onChange={ e => handleChange('email',e)}
name="email"
/>
<input
type="text"
onChange={ e => handleChange('password',e)}
name="password"
/>
Upvotes: 0
Reputation: 876
Thanks to Doğancan Arabacı. I tried to mimic the state and setState from class-based component so the feel doesn't change. My solution looks like this.
const [state , setState] = useState({
email : "",
password : ""
})
const handleChange = e => {
const {name , value} = e.target
setState( prevState => ({
...prevState,
[name] : value
}))
}
Upvotes: 21
Reputation: 34
You can use prevState.
const [state, setState] = useState({ fruit: "", price: "" });
const handleChange = e => {
const { name, value } = e.target;
setState(prevState => ({
...prevState,
[name]: value
}));
};
<input
value={state.fruit}
type="text"
onChange={handleChange}
name="fruit"
/>
<input
value={state.price}
type="text"
onChange={handleChange}
name="price"
/>
Upvotes: 1
Reputation: 123
You can use UseReducer provided by react. It is very simple and it provide a very clean code.
import React, {useReducer} from 'react';
const initialState = {
email: '',
password: ''
};
function reducer(state, action) {
switch (action.type) {
case 'email':
return {
...state,
email: action.payload
};
case 'password':
return {
...state,
password: action.payload
};
default:
throw new Error();
}
}
const Login = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const handleChange = event => dispatch({ type: event.target.name, payload: event.target.value.trim() });
return (
<div className="container">
<div className="form-group">
<input
type="email"
name="email"
value={state.email}
onChange={handleChange}
placeholder="Enter Email"
/>
</div>
<div className="form-group">
<input
type="password"
name="password"
value={state.password}
onChange={handleChange}
placeholder="Enter Password"
/>
</div>
<div className="form-group">
<button className="btn btn-info">Login</button>
</div>
</div>
)
}
export default Login;
Upvotes: 1
Reputation: 2368
// The useState Hook is used in React,
const {obj, setObj} = useState({ // Initially a value of `{}` object type
foo: {},
bar: {}
})
const handle = event => {
setObj({ // here `{}` is a new value of type object
...obj, // So, first use the ... operator to get the prevState
bar: { qux: {} } // then, update
})
}
Upvotes: 1
Reputation: 2679
Try
this.state = { email : "", password : "" }
const handleChange = e => {
const updated = {name , value} = e.target
const next = {...this.state, ...updated}
this.setState({next})
}
Upvotes: 0
Reputation: 3982
You should use setState with callback function:
setState(prev => ({
...prev,
email: 'new mail',
}))
You'll create a new state object, which was created by previous state. And you can override anything you need. You'd need more new objects if you'd have a complex state object.
Upvotes: 20