skip to Main Content

I’m trying to create something where a user can add multiple addresses, with a maximum of 3 and the default one which can’t be removed.

For this example, an address is just a street name and city.

What i’m struggling to figure out is how I can handle the state and the form input fields for this scenario.

I could have 3 separate states for each address and just target the state values in the forms that way, but I’d like to know if you guys have a cleaner approach to doing something like this.

And once the form is submitted, I would want an array of these addresses as they form part of another object in my use case.

Here’s the React code

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

export default function App() {
  const [addressCount, setAddressCount] = useState(1);
  const [address, setAddress] = useState([]);

  const handleAddressChange = (event) => {
    const { name, value } = event.target;
    // handle address state
  };

  const addAddress = () => {
    if (addressCount == 3) {
      alert("you cannot more than 3 addresses");
      return;
    }
    setAddressCount(addressCount + 1);
  };

  const removeAddress = () => {
    if (addressCount == 1) {
      alert("You cannot remove this address");
      return;
    }
    setAddressCount(addressCount - 1);
  };

  const AddressDiv = (index) => {
    return (
      <div key={index} className="address-section">
        <div className="mb-2">
          <label>Street name</label>
          <input
            type="text"
            placeholder="Enter street name"
            name="streetName"
            onChange={handleAddressChange}
          />
        </div>
        <div className="mb-2">
          <label>City</label>
          <input
            type="text"
            placeholder="Enter city name"
            name="streetName"
            onChange={handleAddressChange}
          />
        </div>
        <div className="add-another-address-section mt-3 mb-4">
          <button type="button" onClick={addAddress}>
            + Add another
          </button>
          <button type="button" onClick={removeAddress}>
            - Remove
          </button>
        </div>
      </div>
    );
  };

  return (
    <div className="App">
      DIV COUNT {addressCount}
      <AddressDiv />
      {Array.from({ length: addressCount - 1 }, (_, index) => {
        return <div style={{ margin: "2rem" }}>{AddressDiv(index)}</div>;
      })}
    </div>
  );
}

And here’s a CodeSandbox link of what It roughly looks like.

https://codesandbox.io/p/sandbox/generate-div-8hknk3?file=%2Fsrc%2FApp.js%3A63%2C26

2

Answers


  1. addressCount is redundant state and should be removed as it can always be computed from address (namely, address.length). The code can be simplified like so:

    export default function App() {
      const [address, setAddress] = useState([{ id: 0 }]);
      const id = useRef(0);
      const addAddress = (insertAfter) => {
        if (address.length === 3) alert('you cannot more than 3 addresses');
        else
          setAddress(address.toSpliced(insertAfter + 1, 0, { id: ++id.current }));
      };
      const removeAddress = (index) => {
        if (index === 0) alert('You cannot remove this address');
        else setAddress(address.toSpliced(index, 1));
      };
      const handleChange = (event, index) => {
        setAddress(
          address.map((o, i) =>
            i !== index ? o : { ...o, [event.target.name]: event.target.value }
          )
        );
      };
      return (
        <div className="App">
          DIV COUNT {address.length}
          {address.map((o, index) => (
            <div key={o.id} className="address-section">
              <div className="mb-2">
                <label>Street name</label>
                <input
                  type="text"
                  placeholder="Enter street name"
                  name="streetName"
                  value={o.streetName}
                  onChange={(e) => handleChange(e, index)}
                />
              </div>
              <div className="mb-2">
                <label>City</label>
                <input
                  type="text"
                  placeholder="Enter city name"
                  name="cityName"
                  value={o.cityName}
                  onChange={(e) => handleChange(e, index)}
                />
              </div>
              <div className="add-another-address-section mt-3 mb-4">
                <button type="button" onClick={() => addAddress(index)}>
                  + Add another
                </button>
                <button type="button" onClick={() => removeAddress(index)}>
                  - Remove
                </button>
              </div>
            </div>
          ))}
        </div>
      );
    }
    
    Login or Signup to reply.
  2. Here is a modified example of Unmitigated’s response.

    • Move address section to its own component
    • Utilize callbacks with dependencies
    • Add additional Bootstrap classes
    const { useCallback, useRef, useState } = React;
    
    const ADDRESS_COUNT_MAX = 3;
    
    const AddressSection = ({
      address: { id, cityName, streetName },
      index,
      addAddress,
      removeAddress,
      handleChange
    }) => {
      return (
        <div key={id} className="address-section">
          <div className="form-group row">
            <label className="col-sm-3 col-form-label">Street name</label>
            <div class="col-sm-9">
              <input
                type="text"
                placeholder="Enter street name"
                name="streetName"
                value={streetName}
                onChange={(e) => handleChange(e, index)}
              />
            </div>
          </div>
          <div className="form-group row">
            <label className="col-sm-3 col-form-label">City</label>
            <div class="col-sm-9">
              <input
                type="text"
                placeholder="Enter city name"
                name="cityName"
                value={cityName}
                onChange={(e) => handleChange(e, index)}
              />
            </div>
          </div>
          <div className="add-another-address-section mt-3 mb-4">
            <button
                type="button"
                className="btn btn-primary"
                onClick={() => addAddress(index)}>
              + Add another
            </button>
            {index > 0
              ? (
                <button
                    type="button"
                    className="btn btn-danger"
                    onClick={() => removeAddress(index)}>
              - Remove
            </button>
              ) : null}
          </div>
        </div>
      );
    };
    
    const App = () => {
      const [addressList, setAddressList] = useState([{ id: 0 }]);
    
      const id = useRef(0);
    
      const addAddress = useCallback((insertAfter) => {
        if (addressList.length === ADDRESS_COUNT_MAX) {
          alert(`You cannot more than ${ADDRESS_COUNT_MAX} address(es)`);
        } else {
          setAddressList(currentAddressList =>
            currentAddressList.toSpliced(insertAfter + 1, 0, { id: ++id.current }));
        }
      }, [addressList]);
    
      const removeAddress = useCallback((index) => {
        if (index === 0) {
          alert("You cannot remove this address");
        } else setAddressList(currentAddressList => currentAddressList.toSpliced(index, 1));
      }, []);
    
      const handleChange = useCallback(({ target: { name, value } }, index) => {
        setAddressList(currentAddressList =>
          currentAddressList.map((address, currentIndex) => currentIndex !== index
            ? address
            : { ...address, [name]: value }));
      }, []);
      
    
      return (
        <div className="App">
          <div className="container">
            <form>
              {addressList.map((address, index) => (
                <AddressSection
                  key={index}
                  address={address}
                  index={index}
                  addAddress={addAddress}
                  removeAddress={removeAddress}
                  handleChange={handleChange}
                />
              ))}
            </form>
          </div>
        </div>
      );
    };
    
    ReactDOM.createRoot(document.getElementById("root")).render(<App />);
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css" rel="stylesheet"/>
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search