Raphael Alvarenga
Raphael Alvarenga

Reputation: 1028

Updating React state with Hooks and tags

I'm having a syntax doubt on how to update React state using hooks in 2 situations.

1) I have a state called company and a form that fills it up. In contact section, there are two inputs referring to the company employee (name and telephone number). But if the company has more than one employee to be contacted, there is an "Add More Contact" button, which must duplicate the same inputs (of course, aiming to a second contact). How can I do that? I mean, to generate another index in the array "contacts" inside the state, increment the totalOfContacts inside the object that has that array and create the input tags so user can type the second contact's data?

2) When I type any inputs, the code triggers the handleChange function. The "name" and "city" already update the state because they are simple states. But how can I update the contact name and his telephone number, since they are part of an index of an array inside the state?

The code below is already working and my 2 questions are exactly the two commented lines (lines 20 and 29).

The "Save" button simply console.log the results so we can monitor them.

Thanks for now.

enter image description here

import React, { useState, useEffect } from "react";

export default () => {
    const [company, setCompany] = useState({
        name: "", city: "",
        contact: {
            totalOfContact: 1,
            contacts: [
                {id: 0, contactName: "", telephoneNumber: ""}
            ]
        }
    })

    useEffect(() => {
        console.log("teste");
    })

    const handleChange = item => e => {
        if (item === "contactName" || "telephone") {
            // How can I set company.contact.contacts[<current_index>].contactName/telephoneNumber with the data typed?
        } else {
            setCompany({ ...company, [item]: e.target.value })
        }
    }

    const handleClick = (e) => {
        e.preventDefault();
        if (e.target.value === "add") {
            // How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
        } else {
            console.log(`The data of the company is: ${company}`);
        }
    }

    return (
        <div>
            <form>
                <h3>General Section</h3>
                Name: <input type="text" onChange = {handleChange("name")} value = {company.name} />
                <br />
                City: <input type="text" onChange = {handleChange("city")} value = {company.city} />
                <br />
                <hr />
                <h3>Contacts Section:</h3>
                Name: <input type="text" onChange = {handleChange("contactName")} value = {company.contact.contacts[0].name} />
                Telephone Numer: <input type="text" onChange = {handleChange("telephone")} value = {company.contact.contacts[0].telephoneNumber} />
                <br />
                <br />
                <button value = "add" onClick = {(e) => handleClick(e)} >Add More Contact</button>
                <br />
                <br />
                <hr />
                <button value = "save" onClick = {(e) => handleClick(e)} >Save</button>
            </form>
        </div>
    )
}

Upvotes: 0

Views: 705

Answers (3)

Wong Jia Hau
Wong Jia Hau

Reputation: 3069

To answer your question let us scope down this problem to a much simpler problem, which is how to handle array of contacts.

You just need know the following things:

  1. Map function
  2. How to update array without mutating the original array

I'll use TypeScript so you can understand better.

const [state, setState] = React.useState<{
    contacts: {name: string}[]
}>({contacts: []})

return (
    <div>
        {state.contacts.map((contact, index) => {
            return (
                <div>
                    Name: 
                    <input value={contact.name} onChange={event => {
                      setState({
                          ...state,
                          contacts: state.contacts.map((contact$, index$) => 
                              index === index$
                                 ? {...contact$, name: event.target.value}
                                 : {...contact$}

                          )
                      })
                    }}/>
                </div>
            )
        }}
    </div>
)

Also, this kind of problem is fairly common in React, so understand and memorize this pattern will help you a lot.

Upvotes: 2

ravibagul91
ravibagul91

Reputation: 20755

To update the state value, you can use functional setState,

const handleChange = item => e => {
    //Take the value in a variable for future use
    const value = e.target.value;
    if (item === "contactName" || "telephone") {
        setCompany(prevState => ({
          ...prevState,
          contact: {...prevState.contact, contacts: prevState.contact.contacts.map(c => ({...c, [item]: value}))}
        }))
    } else {
        setCompany({ ...company, [item]: e.target.value })
    }
}

To add new set of input on the click of button you can do this,

const handleClick = (e) => {
    e.preventDefault();
    //This is new set of input to be added
    const newSetOfInput = {id: company.contact.contacts.length, contactName: "", telephoneNumber: ""}
    if (e.target.value === "add") {
        // How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
        setCompany(prevState => ({
          ...prevState,
          contact: {...prevState.contact, contacts: prevState.contact.contacts.concat(newSetOfInput), totalOfContact: prevState.contact.contacts.length + 1}
        }))
    } else {
        console.log(`The data of the company is: ${company}`);
    }
}

Finally you need to iterate over your contacts array like,

{company.contact.contacts && company.contact.contacts.length > 0 && company.contact.contacts.map(contact => (
    <div key={contact.id}>
    Name: <input type="text" onChange = {handleChange("contactName")} value = {contact.contactName} />
    <br/>
    Telephone Numer: <input type="text" onChange = {handleChange("telephoneNumber")} value = {contact.telephoneNumber} />
    </div>
))}

Demo

Note: You should use block elements like div instead of breaking the line using <br/>

Upvotes: 2

Nithin Thampi
Nithin Thampi

Reputation: 3679

You can do something like this.

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";


  const App = () => {
    const [company, setCompany] = useState({
      name: "",
      city: "",
      contact: {
        totalOfContact: 1,
        contacts: [{id: 0, contactName: "", telephoneNumber: ""}]
      }
    });

    console.log(company);

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

    const handleChange = (item, e,index) => {
      if (item === "contactName" || item === "telephoneNumber") {
        const contactsNew = [...company.contact.contacts];
        contactsNew[index] = { ...contactsNew[index], [item]: e.target.value };
        setCompany({
          ...company,
          contact: { ...company.contact, contacts: contactsNew }
        });
        // How can I set company.contact.contacts[<current_index>].contactName/telephoneNumber with the data typed?
      } else {
        setCompany({ ...company, [item]: e.target.value });
      }
    };

    const handleClick = e => {
      e.preventDefault();
      if (e.target.value === "add") {
        const contactNew = {...company.contact};
        contactNew.totalOfContact = contactNew.totalOfContact + 1;
        contactNew.contacts.push({id:contactNew.totalOfContact -1, contactName: "", telephoneNumber: ""});
        setCompany({...company, contact: {...contactNew}});
        // How can I set company.contact.totalOfContact to 2 and create one more set of inputs tags for a second contact?
      } else {
        alert("Push company to somewhere to persist");
        console.log(`The data of the company is: ${company}`);
      }
    };

    return (
      <div>
        <form>
          <h3>General Section</h3>
          Name:{" "}
          <input
            type="text"
            onChange={(e) => handleChange("name", e)}
            value={company.name}
          />
          <br />
          City:{" "}
          <input
            type="text"
            onChange={(e) => handleChange("city", e)}
            value={company.city}
          />
          <br />
          <hr />
          <h3>Contacts Section:</h3>
          {company.contact.contacts.map((eachContact, index) => {
            return <React.Fragment>
                Name:{" "}
                <input
                  type="text"
                  onChange={(e) => handleChange("contactName",e, index)}
                  value={eachContact.name}
                />
                Telephone Numer:{" "}
                <input
                  type="text"
                  onChange={(e) => handleChange("telephoneNumber",e, index)}
                  value={eachContact.telephoneNumber}
                />
              <br />
            </React.Fragment>
                })}

          
          <br />
          <button value="add" onClick={e => handleClick(e)}>
            Add More Contact
          </button>
          <br />
          <br />
          <hr />
          <button value="save" onClick={e => handleClick(e)}>
            Save
          </button>
        </form>
      </div>
    );
  };


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

Your state structure looks like an ideal candidate for useReducer hook. I would suggest you try that instead of useState. Your code should look muck readable that way, I suppose. https://reactjs.org/docs/hooks-reference.html#usereducer

Upvotes: 1

Related Questions