skip to Main Content

I am building a website using react where the users should be able to answer questions. some of these questions may have multiple answers so I want the website to dynamically add and remove input fields for these answers. Here is an example to clarify: Initially there will be only 1 input field visible for a question but when the user starts typing in this first input field a second (empty) input field should appear and when he starts typing in the second input field, a third one should appear and so on.

Adding input fields already works fine but empty input fields (when an answer is removed) should also be removed and this is where the problems start.

I am rendering the answers based on the "answers" array using a map function:

{answers.map((answer, index) => (
                      <div key={index}>
                        <label htmlFor={`answer${index + 1}`}>{`answer ${index + 1}:`}</label>
                        <input
                          type="text"
                          className="w-full h-10 px-3 text-base placeholder-gray-600 border rounded-            lg focus:shadow-outline"
                          onChange={(e) => handleAnswerChange(index, e.target.value)}
                        />
                      </div> 
                    ))}

and this is the "handleanswerchange" function:

const [answers, setAnswers] = useState(['']);

  const handleAnswerChange = (index: number, value: string) => {
    console.log('Index:', index);
    console.log('Value:', value);

    setAnswers(prev => {
      const updatedAnswers = [...prev];
      updatedAnswers[index] = value;

      if (value.trim() === '') {
        updatedAnswers.splice(index, 1);
      }
  
      if (index === updatedAnswers.length - 1 && value.trim() !== '') {
        updatedAnswers.push('');
      }
      return updatedAnswers;
    })

    setAnswers(prev => [...prev]);
  };

  useEffect(() => {
    console.log(answers);
  }, [answers]);

as you can see I am logging the ‘answers’ array. The logs show that the logic is working fine and the array gets updated correctly when an answer is removed but the ui does not reflect these changes.

For example: when I give inputs ‘a’, ‘z’, ‘e’, ‘r’ there are 5 input fields visible. 4 of them contain the inputs and one of them (the last one) is empty. This is correct and what I would expect. When I remove the content of the second input field (in this case ‘z’), this input field should be removed. The array that gets logged is [‘a’,’e’,’r’,”]. This is also correct but the ui does not reflect this array.

Instead, the now empty, second input field remains visible and the empty input field at the end gets removed. I tried using a combination of index and answer as the key attribute for the div, I have tried using uuidV4 to generate unique keys, I have tried rewriting the ‘handleanswerChange’ function about a dozen time, … Nothing seems to work, so I hope one of you guys can help me out. Thanks in advance!

2

Answers


  1. The only issue you had is regarding for omitting value attribute off the input element. Of course it won’t work if you don’t track the state in the input element.

    Here is my complete code, you can use it in some React playground as well.

    import React, { useState } from 'react';
    
    export function App(props) {
      const [answers, setAnswers] = useState(['']);
    
      const handleAnswerChange = (index, value) => {
        setAnswers((prev) => {
          const updatedAnswers = [...prev];
          updatedAnswers[index] = value;
    
          if (value.trim() === '') {
            updatedAnswers.splice(index, 1);
    
            console.log(updatedAnswers);
    
            return updatedAnswers;
          }
    
          if (index === updatedAnswers.length - 1 && value.trim() !== '') {
            updatedAnswers.push('');
    
            return updatedAnswers;
          }
    
          return updatedAnswers;
        });
      }
    
      return answers.map((answer, index) => (
        <div key={index}>
          <label htmlFor={`${answer}${index + 1}`}>{`answer ${index + 1}:`}</label>
          <input
            type="text"
            onChange={(e) => handleAnswerChange(index, e.target.value)}
            value={answer}
          />
        </div> 
      ));
    }
    

    Off the question itself, you will also counter a bug when you just have 1 input, and simply write the "space" char on this input because you will the delete the input.

    That said you should modify a bit the logic of the handleAnswerChange:

      const handleAnswerChange = (index, value) => {
        setAnswers((prev) => {
          const updatedAnswers = [...prev];
          updatedAnswers[index] = value;
    
          if (index === updatedAnswers.length - 1 && value.trim() !== '') {
            updatedAnswers.push('');
    
            return updatedAnswers;
          }
    
          if (index !== updatedAnswers.length - 1 && value.trim() === '') {
            updatedAnswers.splice(index, 1);
    
            console.log(updatedAnswers);
    
            return updatedAnswers;
          }
    
          return updatedAnswers;
        });
      }
    
    Login or Signup to reply.
  2. here is the thing:
    The problem is you are looking in the wrong place, to add a new input when there is value in the preview one, all seems good.
    Now here is the answer you have to make to yourself: Where do I have to look to decide if I need to remove the last element?
    The answer is: the one before the last input

    array[array.lenght - 2]
    

    If this is empty, remove the last position in the array

    const [state, setState] = useState(['']);
    
        useEffect(() => {
            if(state.length > 0) {
    //if the las element has value, add a new empty element
                if (state[state.length - 1].trim() !== '') {
                    setState(prev => [...prev, ''])
                } else {
    //if the one before last is empty, remove the last one
                    if(state.length > 1 && state[state.length - 2].trim() === '') {
                        setState(prev => {
                            const tempPrev = [...prev]
                            tempPrev.pop()
                            return tempPrev
                        })
                    }
                }
            }
        }, [state]);
    
    //update the current position
    const handleChange = (e) => {
            setState(prev => {
                const tempPrev = [...prev]
                const key = e.target.name
                tempPrev[key] = e.target.value
                return tempPrev
            })
        }
    
    //inside a react component
    {state.map((item, index) => {
                    return <input
                        key={index}
                        onChange={handleChange}
                        value={item}
                        name={index.toString()}
                    />
                })}
    

    Advice: Avoid mixing logic, at least when you are starting, React can be a little messy.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search