skip to Main Content

I’m working on react-phonebook app. I’m unable to understand the correct logic to edit the contact details.
On clicking the edit button, I need to show the form and put name and number of that particular contact in the input fields and then take user input as the edited contact and display it in the list.
I’m facing the following issues:

If showForm is false, it gives the error

Cannot set properties of null (setting 'value')

although I’ve added setShowForm(true) in handleEdit function but it still shows error but works when the form is already showing.
Here is the code:

import React, { useState } from "react";

const App = () => {
  let phoneBookData = [{ name: "John Doe", number: 12345678 }];
  const [contacts, setContacts] = useState(phoneBookData);
  const [showForm, setShowForm] = useState(false);
  const [userName, setUserName] = useState("");
  const [number, setNumber] = useState("");

  const handleDelete = (index) => {
    console.log("index:", index);
    let selectedIndex = index;
    let newContacts = contacts.filter(
      (_contact, index) => index !== selectedIndex
    );
    setContacts(newContacts);
  };

  const handleAddContact = () => {
    if (userName.length === 0) {
      alert("Please enter a valid name.");
      return;
    } else if (userName.includes("1")) {
      alert("Name cannot contain any number or special character.");
      return;
    } else if (number.length === 0) {
      alert("Please enter a valid number.");
      return;
    }

    let newContact = { name: userName, number: number };
    setContacts([...contacts, newContact]);
    setUserName("");
    setNumber("");
  };

  const handleEdit = (index) => {
    setShowForm(true);
    document.getElementById("number").value = number;
    document.getElementById("name").focus();
    document.getElementById("name").value = userName;
  };

  return (
    <div id="main">
      <div className="container-fluid">
        <div className="row">
          <div className="col-md-4"></div>

          <div className="col-md-4">
            <div className="App">
              <h2 className="header">PhoneBook App</h2>
              <span
                style={{
                  cursor: "pointer",
                  color: "blue",
                  textDecoration: "underline",
                }}
                onClick={() => setShowForm(!showForm)}
              >
                {showForm ? "Hide Contact Form" : "Create New Contact"}
              </span>
              {showForm && (
                <div className="container">
                  <form className="form">
                    <div className="form-group">
                      <input
                        type="text"
                        className="form-control"
                        placeholder="Name"
                        value={userName}
                        onChange={(e) => setUserName(e.target.value)}
                        id="name"
                      />
                    </div>
                    <div className="form-group">
                      <input
                        type="text"
                        className="form-control"
                        placeholder="Number"
                        value={number}
                        onChange={(e) => setNumber(e.target.value)}
                        id="number"
                      />
                    </div>
                    <button
                      type="button"
                      className="btn btn-primary icon-holder"
                      onClick={handleAddContact}
                    >
                      Add
                    </button>
                  </form>
                </div>
              )}
              <p>There are {contacts.length} contacts saved in this book.</p>
              {contacts.map((contact, index) => (
                <div key={index} className="contacts">
                  <div className="contact-details">
                    <h5>
                      {index + 1}) {contact.name}
                    </h5>
                    <p>{contact.number}</p>
                  </div>
                  <button
                    className="icon-holder"
                    onClick={() => handleEdit(index)}
                  >
                    Edit
                  </button>
                  <button
                    className="icon-holder"
                    onClick={() => {
                      handleDelete(index);
                    }}
                  >
                    Delete
                  </button>
                  <hr />
                </div>
              ))}
            </div>
          </div>

          <div className="col-md-4"></div>
        </div>
      </div>
    </div>
  );
};

export default App;

Here is the link to codesandbox: https://codesandbox.io/p/sandbox/todos-app-listed-rxsd3p?file=%2Fsrc%2FApp.js%3A6%2C51

4

Answers


  1. Don’t use DOM selectors like document.getElementById.

    In your case, the selectors return null because the corresponding elements are not yet on the page when you try to select them.

    Solution: get rid of the three document.getElementById calls, and add the autoFocus prop to the field you want to focus.

    If you need a reference to an actual DOM element, read into useRef.

    Login or Signup to reply.
  2. you can manage the form state using React state management and check that the form input are controlled component. When clicking the edit button, populate the form field with the details of the selected contact. Upon updating the contact details, handle the state updates appropriately to reflect the changes in the contact list.
    I hope it help.

    Login or Signup to reply.
  3. This is where you are getting the error:

      const handleEdit = (index) => {
        setShowForm(true);
        document.getElementById("number").value = number;
        document.getElementById("name").focus();
        document.getElementById("name").value = userName;
      };
    

    You are trying to get form elements of the form that you are not yet showing (async function call most probably) and you consequently get a null and you therefore cannot set the value of null.

    Try something like this:

      const handleEdit = (index) => {
        setShowForm(true);
        setNumber(number);
        setUserName(userName);
      };
    

    and add autoFocus to the item you want.

    Login or Signup to reply.
  4. You can add useEffect for handling document updating if you want to access DOM elements directly

      useEffect(() => {
         if (showForm) {
            document.getElementById("number").value = number;`
            document.getElementById("name").focus();
            document.getElementById("name").value = userName;
        }
      }, [showForm]) 
    

    you can also add refs for better access to DOM elements

      useEffect(() => {
        if (showForm) {
           numberInputRef.current.value = number;
           usernameInputRef.current.focus();
           usernameInputRef.current.value = userName;
        }
      }, [showForm])
    

    this will help you to change the values of inputs automatically every time the showForm state is changed and also ensure the DOM has updated before accesing DOM elements

    or you can use controlled inputs instead to avoid this kind of bugs

      const handleEdit = useCallback((contact) => {
        setShowForm(true);
        if (contact) {
          setNumber(contact.number);
          setUserName(contact.name);
        }
      }, [setShowForm, setNumber, setUserName]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search