简体   繁体   中英

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.

在此处输入图像描述

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>
    )
}

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

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.

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/>

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM