Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to clear input values of dynamic form in react

I have a dynamic form as a functional component which is generated via a class based component. I want to make reset button which clears the input field values and sets the state to null array.

Full code is available here: https://codesandbox.io/s/beautiful-archimedes-o1ygt

I want to make a reset button, clearing all the input values and initializing the Itemvalues array to null. Even if I set the values to null, it doesn't clear the input field.

However, the problem I'm facing is that since, it is a dynamic form and a functional component it doesn't have a predefined state for each individual form field making it difficult to set value to null. Can someone please help, I'm stuck on this from a long time

like image 608
user8306074 Avatar asked Jun 23 '19 13:06

user8306074


3 Answers

Here's a codesandbox to show you how to reset the items: https://codesandbox.io/s/romantic-heisenberg-93qi7

I also left a note for you on how to get this to work with your API data, see the comment inside onChangeText()

The problem is that the inputs are not controlled by state as you have deduced. We should create an updated object for each item from your API, giving it a value prop.

index.js

import React from "react";
import ReactDOM from "react-dom";
import Cart from "./Cart";

import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      Items: [],
      itemvalues: [{}]
    };
    this.onChangeText = this.onChangeText.bind(this);
    this.getItems = this.getItems.bind(this);
    this.handleReset = this.handleReset.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.findFieldIndex = this.findFieldIndex.bind(this);
    this.trimText = this.trimText.bind(this);
  }

  getItems = () => {
    /*if the data is coming from an API, store it in an array then .map() over it.
     we can add a value prop to the object like:
      so you can do something like:

      const newItems = [...apiData].map((item) => {
        return {
          ...item,
          value: ""
        }
      })

      this.setState({
        Items: newItems
      })
     */

    this.setState({
      Items: [
        {
          name: "item1",
          description: "item1",
          group: "groupA",
          dtype: "str",
          value: ""
        },
        {
          name: "item2",
          description: "item2",
          group: "groupA",
          dtype: "str",
          value: ""
        },
        {
          name: "item3",
          description: "item3",
          group: "groupB",
          dtype: "str",
          value: ""
        },
        {
          name: "item4",
          description: "item4",
          group: "groupB",
          dtype: "str",
          value: ""
        }
      ]
    });
  };

  onChangeText = e => {
    const updatedItems = [...this.state.Items].map(item => {
      if (item.name === e.target.name) {
        return {
          ...item,
          value: e.target.value
        };
      } else {
        return item;
      }
    });

    const updatedItemValues = [...updatedItems].reduce((obj, curr) => {
      if (!obj[curr.group]) {
        obj[curr.group] = [];
      }
      obj[curr.group] = [...obj[curr.group], { [curr.name]: curr.value }];
      return obj;
    }, {});

    this.setState({
      ...this.state,
      Items: updatedItems,
      itemvalues: updatedItemValues
    });
  };

  findFieldIndex = (array, name) => {
    return array.findIndex(item => item[name] !== undefined);
  };
  trimText(str) {
    return str.trim();
  }

  handleReset = () => {
    const resetedItems = [...this.state.Items].map(item => {
      return {
        ...item,
        value: ""
      };
    });

    this.setState(
      {
        ...this.state,
        Items: resetedItems,
        itemvalues: []
      },
      () => console.log(this.state)
    );
  };

  handleSubmit = () => {
    console.log(this.state.itemvalues);
  };

  render() {
    return (
      <div>
        {
          <Cart
            Items={this.state.Items}
            getItems={this.getItems}
            handleSubmit={this.handleSubmit}
            handleReset={this.handleReset}
            onChangeText={this.onChangeText}
          />
        }
      </div>
    );
  }
}

Cart.js

import React, { useEffect } from "react";
import Form from "./Form";

const Cart = props => {
  useEffect(() => {
    props.getItems(props.Items);
  }, []);

  return (
    <div>
      <Form Items={props.Items} onChangeText={props.onChangeText} />

      <button onClick={props.handleSubmit}>Submit</button>
      <button onClick={props.handleReset}>Reset</button>
    </div>
  );
};

export default Cart;

The Cart component can remain mostly the same, we do not need to pass in props.items to useEffect() dependency.

Form.js

import React from "react";

const Form = props => {
  return (
    <div>
      {props.Items.map(item => {
        return (
          <input
            name={item.name}
            placeholder={item.description}
            data-type={item.dtype}
            data-group={item.group}
            onChange={e => props.onChangeText(e)}
            value={item.value}
          />
        );
      })}
    </div>
  );
};
export default Form;

Now in Form component, we provide each input a value prop that is connected to the item our upper-most parent component-state.

That's pretty much all you need to reset the values.

like image 114
Chris Ngo Avatar answered Oct 16 '22 03:10

Chris Ngo


See if that works for you:

Working example on CodeSandbox

enter image description here

Since you were already using hooks in part of your code, I've converted your class into a functional component using hooks (my advice: learn hooks and forget about class components).

I've added a value property to your INITIAL_STATE so it will keep the input value for each inputItem.

Full CODE:

index.js

import React, { useState } from "react";
import ReactDOM from "react-dom";
import FormV2 from "./FormV2";

import "./styles.css";

function App() {
  const INITIAL_STATE = [
    {
      name: "item1",
      description: "item1",
      group: "groupA",
      dtype: "str",
      value: "" // ADDED VALUE PROPERTY TO KEEP THE INPUT VALUE
    },
    {
      name: "item2",
      description: "item2",
      group: "groupA",
      dtype: "str",
      value: ""
    },
    {
      name: "item3",
      description: "item3",
      group: "groupB",
      dtype: "str",
      value: ""
    },
    {
      name: "item4",
      description: "item4",
      group: "groupB",
      dtype: "str",
      value: ""
    }
  ];

  const [inputItems, setInputItems] = useState(INITIAL_STATE);

  function handleChange(event, index) {
    const newValue = event.target.value;
    setInputItems(prevState => {
      const aux = Array.from(prevState);
      aux[index].value = newValue;
      return aux;
    });
  }

  function handleReset() {
    console.log("Reseting Form to INITIAL_STATE ...");
    setInputItems(INITIAL_STATE);
  }

  function handleSubmit() {
    inputItems.forEach(item =>
      console.log(
        "I will submit input: " + item.name + ", which value is: " + item.value
      )
    );
  }

  return (
    <FormV2
      handleSubmit={handleSubmit}
      handleReset={handleReset}
      handleChange={handleChange}
      inputItems={inputItems}
    />
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

FormV2.js

import React from "react";

function FormV2(props) {
  const formInputItems = props.inputItems.map((item, index) => (
    <div key={item.name}>
      {item.name + ": "}
      <input
        type="text"
        data-type={item.dtype}
        data-group={item.group}
        placeholder={item.description}
        value={item.value}
        onChange={event => props.handleChange(event, index)}
      />
    </div>
  ));

  return (
    <React.Fragment>
      <form>{formInputItems}</form>
      <button onClick={props.handleSubmit}>Submit</button>
      <button onClick={props.handleReset}>Reset</button>
      <div>State: {JSON.stringify(props.inputItems)}</div>
    </React.Fragment>
  );
}

export default FormV2;
like image 3
cbdeveloper Avatar answered Oct 16 '22 03:10

cbdeveloper


In order to control the values of the child components (Items) which I presume are input fields you need to be passing down their values from their parent component. So each of your items will have an item.value which is stored in the parent component's state.

That means that in the parent component you will be able to define a method which clears all of the item values it is storing in its state. That will probably look something like

resetInputs = () => {
    this.setState({
        inputFields: this.state.inputFields.map(inputField => {
            ...inputField,
            value: ''
        }
    })
}

Also you'll need to write what kind of tag you want for your code to work, like input.

So what you'll end up with for the code of the child component you shared is something like:

const Form = (props) => {
    return (
    <div>
        {props.Items.map(item => (
          <input
            name={item.name}
            value={item.value}
            placeholder={item.description}
            onChange={e => props.onChangeText(e)}
          /> 
        )
        )}
      </div> 
    );
}
export default Form 
like image 2
pteranobyte Avatar answered Oct 16 '22 02:10

pteranobyte