0

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='johndoe09@example.ex' 
          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 :) !

2 Answers2

0

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
      }
    });
  }
RKataria
  • 581
  • 1
  • 5
  • 12
0

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.

Simon L
  • 166
  • 4