i'm coding a dynamic react form. I want to be able to print a field value, update the state according to it's value changes, and all of that while i'm typing inside that field. I also want to print the state in the console by clicking on the submit button. Not that hard, i know, and i did it (almost perfect). Here is how i did :
- i declared and initial state, with inside and object called "inputs", with 3 attributes, name, email and password.
- i declared my reducer with one switch case, with the action type "SETINPUTS", that returns the state and its inputs object set to the action payload.
- i declared a constant "inputs" assigned to a useState with initial value set to the initial state.inputs.
- i declared a "state" constant assigned to a useReducer initialized with my initial state.
- then, i declared two functions : handleChange() that update "inputs"(useState const) and dispatch the "SETINPUTS" action with a payload equal to "inputs"; handleSubmit() that prevent the default event of submitting and print the state in the console.
- and, at last, for each field, i set the value attribute to the corresponding "inputs" value and the onChange to the handleChange() function.
here is my code itself :
import React, { useReducer, useState } from 'react' import './Form.css' const initialState = { inputs : { name: "", email: "", password: "" } }; const reducer = (state, action) => { switch(action.type){ case 'SETINPUTS' : return { ...state, inputs: action.payload }; default: return state; } } const Form = () => { const [inputs, setInputs] = useState(initialState.inputs) const [state, dispatch] = useReducer(reducer, initialState) const handleSubmit = (e) => { e.preventDefault(); console.log(state) } function handleChange(e){ setInputs({ ...inputs, [e.target.name]: e.target.value }); dispatch({ type: 'SETINPUTS', payload: inputs }) } return ( <div className='form'> <form onSubmit={handleSubmit}> <label htmlFor="name">Name</label> <input type="text" name='name' placeholder='John Doe' value={inputs.name} onChange={handleChange} /> <label htmlFor="email">Email</label> <input type="email" name='email' placeholder='[email protected]' value={inputs.email} onChange={handleChange} /> <label htmlFor="password">Password</label> <input type="password" name='password' placeholder='enter your password please' value={inputs.password} onChange={handleChange} /> <button type='submit'>Submit</button> </form> <div> {state.inputs.name} {state.inputs.email} {state.inputs.password} </div> </div> ) } export default Form
The problem, as you can see on this picture, is that what i type is not exactly what is returned or print in the console. i have to delete the last letter, to add a space for the last letter to be taken into account. When i delete the last character, it remains, etc ... It's quite strange, i need your help please.
THANKS A LOT :) !
CodePudding user response:
The Issue is with your handleChange method. Setting state is a async method... so you will not get the latest value in state. Try below implementation:
function handleChange(e) {
setInputs({ ...inputs, [e.target.name]: e.target.value });
}
useEffect(() => {
dispatch({ type: "SETINPUTS", payload: inputs });
}, [inputs]);
or this one:
function handleChange(e) {
setInputs({ ...inputs, [e.target.name]: e.target.value });
dispatch({
type: "SETINPUTS",
payload: {
...inputs,
[e.target.name]: e.target.value
}
});
}
CodePudding user response:
In your handleChange
function, setInputs
is an async operation, meaning the dispatch
call will not be using the latest state of inputs.
You could instead pass in e.target.value
directly:
function handleChange(e){
setInputs({ ...inputs, [e.target.name]: e.target.value });
dispatch({ type: 'SETINPUTS', payload: {...inputs, [e.target.name]: e.target.value }});
}
However, you may want to rethink your design to depend either on the component's state, or the reducer store only, rather than having both and trying to keep them in sync.