Home > Software engineering >  How to get values of dynamic/multiple fields on formsubmit with React?
How to get values of dynamic/multiple fields on formsubmit with React?

Time:10-11

I have a form where the user can add Textfields in order to add more items, and I'm struggling to get the items out of the form when they submit it.

The Textfields are created by a component function (as below) and the whole form is not contained in one function, as I've seen in many examples, which means that I can't simply store the values as a state and read them later (I think..?).

The fields are generated with the following code:

const itemNameTextFields = () => {
    const [itemNames, setItemNames] = React.useState(['']);

    const addItemNameField = () => setItemNames([...itemNames, '']);
    const handleInputChange = (i, e) => {
        const values = [...itemNames];
        values[i] = e.target.value;
        setItemNames(values);
    }

    return (
        itemNames.map((itemName, i) => (
            <Box component='span' key={`item_${i}`} sx={{display: 'flex', flexWrap: 'wrap'}}>
                <TextField
                    value={itemName}
                    onChange={e => handleInputChange(i, e)}
                    label={`Item ${i 1}`}
                    name={`item_${i}`}
                    className={classes.itemName}
                />
                {itemNames.length-1 === i &&
                    <Button
                        endIcon={<Icon icon={plusCircleOutline} />}
                        onClick={addItemNameField}
                    >
                        <small>Add another</small>
                    </Button>
                }
            </Box>
        ));
    );
}

And my code for handling the form being submitted is something like this:

const handleSubmit = e => {
    e.preventDefault();
    let obj = {
        /* ... */
        name                : e.target['name'].value || '',
        items               : [] //this is what I am missing
    };
    console.log(obj);
}

This may not be a good way of doing this, however.

What I'd like to achieve is either:

  • for items within obj to be an array containing the values of each Textfield from the first code block. E.g obj = {items: ['item1', 'item2',/ .../]} or
  • for obj to contain each item individually. E.g obj = {item_1: 'item1', item_2: 'item'2, /.../}

Ideally without any third-party libraries where a solution can be found without them.

One possible 'solution' I've thought about would be having a hidden input element with a state that'd contain the array and then getting the value of that on formsubmit, rather than each individual field, but this results in the array being 'sent' as a comma-separated list, which could cause issues if any of the values have commas in.

Alternatively, finding a way to pass the value of each input field to handleSubmit (perhaps through props?) could work as well, but I'm not quite sure how to achieve this.

Thanks :)

CodePudding user response:

You can add a redux store to pass the parameters as follows;

    const React = window.React;
    const ReactDOM = window.ReactDOM;
    const {Box, Button, TextField} = window.MaterialUI;
    const Redux = window.Redux;
    
  
    function itemReducer(state = [], action) {
      switch (action.type) {
        case 'SET':
            let tmp = [...state];
          tmp[action.index] = action.item;
          return state = tmp;
        default:
          return state;
      }
    }
    
    const store = Redux.createStore(itemReducer, [])
    
    const handleSubmit = e => {
        e.preventDefault()
        let obj = {
            name                : e.target.innerHTML || '',
            items               : store.getState() 
        }
        console.log(obj)
    }
    
    const App = () => {
    
      const itemNameTextFields = () => {
    
          const [itemNames, setItemNames] = React.useState([''])
    
          const addItemNameField = () => {
              setItemNames([...itemNames, '']);
          }
    
          const handleInputChange = (i, e) => {
              store.dispatch({
                type:"SET",
                index:i,
                item:e.target.value
              });
              const values = [...itemNames]
              values[i] = e.target.value
              setItemNames(values)
          }
    
          return (
              itemNames.map((itemName, i) => (
                  <Box component='span' key={"item_"   i} sx={{display: 'flex', flexWrap: 'wrap', marginBottom:1}}>
                      <TextField
                          value={itemName}
                          onChange={e => handleInputChange(i, e)}
                          label={"Item "   i}
                          name={"item"   i}
                          variant="outlined"
                          fullWidth
                      />
                      {itemNames.length-1 === i &&
                          <Button
                              size='small'
                              onClick={addItemNameField}
                              disabled={!itemNames[itemNames.length-1].length > 0}
                              style={{marginLeft:4}}
                          >
                              <small>Add another</small>
                          </Button>
                      }
                  </Box>
              ))
          )
      }
    
      return itemNameTextFields();
    }
    
    ReactDOM.render(
        <div><App/><Button onClick={handleSubmit}>Submit</Button></div>, 
        document.getElementById('root')
    );
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/[email protected]/dist/redux.js"></script>
<script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script>

<div id="root"></div>

CodePudding user response:

Because the name of the function won't allow you render directly as a Component, I thought you could just create another function to get the values, and call that whenever you need it

const itemNameTextFields = () => {
  ...
  const getItems = () => itemNames;

  return {
    getItems,
    Fields: (...)
  };
}

And you can call the getItems function where the handleSubmit is defined

const App = () => {
  const { getItems, Fields } = itemNameTextFields();

  const handleSubmit = e => {
      e.preventDefault();
      let obj = {
          ...,
          items: getItems()
      };
  }

  return (
    <form onSubmit={handleSubmit}>
      <Fields />
    </form>
  );
}

CodePudding user response:

You could keep track of the items in the element that renders the form, in other words move the usestate to the form element. The pass the state value and setter to the child texfield elements and call the statesetter instead of letting those elements have their own state.

You should be able to access that state within your onsubmit

  • Related