1

I'm trying to create a component for my app, where when I click on a button input field opens, after I add text I click on that button again and another input opens up, and so on. Then e.target.value of all those inputs should be saved in a different state and displayed in another components. So far I was able to create such input, but I can't figure out how to edit input fields. My code:

import "./styles.css";
import React, { useState } from "react";

export default function App() {
  const [input, setInput] = useState([{}]);
  const [data, setData] = useState([]);

  function handleChange(i, e) {
    e.preventDefault();

    setInput([
      {
        id: i,
        value: e.target.value
      }
    ]);
  }

  function handleAddInput() {
    const values = [...input];
    values.push({ value: null });
    setInput(values);
  }

  const handleSave = () => {
    let value = input?.map((item) => {
      return item.value;
    });
    if (!value || /^\s*$/.test(value)) {
      return;
    }

    const newData = [...data, ...input];

    setData(newData);

    setInput([])
  };

  return (
      <div className="App">
        <button type="button" className="btn" onClick={() => handleAddInput()}>
          Add  Input fields
        </button>

        {input?.map((input, idx) => {
          return (
            <div key={input.id}>
              <input
                type="text"
                placeholder="Enter text"
                onChange={(e) => handleChange(idx, e)}
                value={input.value}
              />
            </div>
          );
        })}

       
    
<h2>Display Added Fields and Edit</h2>
      {data?.map((item) => {
        return (
          <>
            <input defaultValue={item.value}
            
            
            
            />
          </>
        );
      })}

<button className="btn" onClick={handleSave}>Save</button>

      {data?.map((item) => {
        return (
          <div style={{ display: "flex", flexDorection: "column", marginTop:"20px" }}>
            {item.value}
          </div>
        );
      })}
        </div>
  );
}

codeSandbox

enter image description here

At the moment when I click "Add Input fields" new input pops up, I enter any input text, then I click Add Input fields again and another input opens up, when I click "Save" button, all input values are saved to state (as data) and displayed as inputs, and after I can map through "data" and display received inputs. Like on image I added "first", then "second" and they're getting displayed fine, but I don't know how I could edit them, for example change "first" to "third" and on Save button "third" should be displayed instead of "first". Any help and suggestions are greatly appreciated.

1

1 Answer 1

1

Issue

  1. Using an index as an id isn't a good idea, they aren't unique.
  2. handleChange doesn't persist existing input state.
  3. Other various issues with React keys, and copying state from input to data

Solution

  1. You don't need input objects, you can store the string primitives from the inputs. This means you also don't need the id property, it is trivial to update by index.
  2. Should use functional state updates to update from previous state.
  3. I suggest using a form and onSubmit handler to handle updating the data state array values.

Code:

function App() {
  const [input, setInput] = useState([]);
  const [data, setData] = useState([]);

  function handleChange(i, e) {
    e.preventDefault();

    setInput((values) =>
      values.map((value, index) => (index === i ? e.target.value : value))
    );
  }

  function handleAddInput() {
    setInput((input) => input.concat(""));
  }

  const saveHandler = (e) => {
    e.preventDefault();

    // Map all existing form field values
    setData((data) => data.map((_, i) => e.target[i].value));

    // If there are any input values, add these and clear inputs
    if (input.length) {
      setData((data) => data.concat(input));
      setInput([]);
    }
  };

  return (
    <div className="App">
      <button type="button" className="btn" onClick={handleAddInput}>
        Add Input fields
      </button>

      {input.map((input, idx) => {
        return (
          <div key={idx}>
            <input
              type="text"
              placeholder="Enter text"
              onChange={(e) => handleChange(idx, e)}
              value={input.value}
            />
          </div>
        );
      })}

      <h2>Display Added Fields and Edit</h2>
      <form onSubmit={saveHandler}>
        {data.map((item, i) => {
          return (
            <div key={i}>
              <input id={i} defaultValue={item} />
            </div>
          );
        })}

        <button className="btn" type="submit">
          Save
        </button>
      </form>

      {data.map((item, i) => {
        return (
          <div
            key={i}
            style={{
              display: "flex",
              flexDirection: "column",
              marginTop: "20px"
            }}
          >
            {item}
          </div>
        );
      })}
    </div>
  );
}

Demo

Edit how-to-edit-input-value-in-react-js

Edit

Edit to generate GUIDs and persist id property through to the data state.

  1. Generate a new GUID when adding a new input field.
  2. Use the id for matching elements for any value updates.
  3. Simply copy the input array to data when saving the input fields.
  4. In example I also render the id at every step so it's clear the data elements are still the same objects.

Code:

function App() {
  const [input, setInput] = useState([]);
  const [data, setData] = useState([]);

  const handleChange = (id) => (e) => {
    e.preventDefault();

    setInput((values) =>
      values.map((el) =>
        el.id === id
          ? {
              ...el,
              value: e.target.value
            }
          : el
      )
    );
  };

  function handleAddInput() {
    setInput((input) =>
      input.concat({
        id: uuidV4(),
        value: ""
      })
    );
  }

  const saveHandler = (e) => {
    e.preventDefault();

    setData((data) =>
      data.map((el) => ({
        ...el,
        value: e.target[el.id].value
      }))
    );

    if (input.length) {
      setData((data) => data.concat(input));
      setInput([]);
    }
  };

  return (
    <div className="App">
      <button type="button" className="btn" onClick={handleAddInput}>
        Add Input fields
      </button>

      {input.map((input) => {
        return (
          <label key={input.id}>
            {input.id}
            <input
              type="text"
              placeholder="Enter text"
              onChange={handleChange(input.id)}
              value={input.value}
            />
          </label>
        );
      })}

      <h2>Display Added Fields and Edit</h2>
      <form onSubmit={saveHandler}>
        {data.map((item) => {
          return (
            <div key={item.id}>
              <label>
                {item.id}
                <input id={item.id} defaultValue={item.value} />
              </label>
            </div>
          );
        })}

        <button className="btn" type="submit">
          Save
        </button>
      </form>

      {data.map((item) => {
        return (
          <div
            key={item.id}
            style={{
              display: "flex",
              flexDirection: "column",
              marginTop: "20px"
            }}
          >
            <div>
              {item.id} - {item.value}
            </div>
          </div>
        );
      })}
    </div>
  );
}

Demo 2

Edit how-to-edit-input-value-in-react-js (forked)

Sign up to request clarification or add additional context in comments.

4 Comments

thank you, this is very helpful. Just one question, how do I add id to [data] and make it an object? Like this {id: 1, value: input}
@new_reactjs Please check this updated codesandbox It uses GUIDs for id properties. Instead of using a passed index it matches on id for updating input values.
thank you for updated code, checked and input has an id, but not the data. I'm sorry to keep asking, but I'm not sure how to add id to data, not input.
@new_reactjs Instead of stripping it out you copy the entire contents of input into data. This requires updating the display of the data array elements since they will now be objects as well. I updated my answer with the updated code from the second codesandbox. Please check again, and hopefully this makes it much clearer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.