0

I am working on my first React program, it is the one provided by Sololearn (Contact Manager). I am trying to add a function to search for a contact: SearchName. However, I need to click many times on the Search button for it to work. Can someone please tell me where I went wrong? For example, typing James Smith in the enter a name to search field first gives "is not in list". Then when clicked again, it updates to is in list.

Here is the code:

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

function AddPersonForm(props) {
  const [person, setPerson] = useState("");

  function handleChange(e) {
    setPerson(e.target.value);
  }

  function handleSubmit(e) {
    if (person !== "") {
      props.handleSubmit(person);
      setPerson("");
    }
    e.preventDefault();
  }
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Add new contact"
        onChange={handleChange}
        value={person}
      />
      <button type="submit">Add</button>
    </form>
  );
}
function RemovePersonForm(props) {
  const [person, setPerson] = useState("");
  function handleChange(e) {
    setPerson(e.target.value);
  }
  function handleSubmit(e) {
    props.handleSubmit(person);
    setPerson("");
    e.preventDefault();
  }
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="enter name to delete"
        onChange={handleChange}
      />
      <button type="submit">Delete</button>
    </form>
  );
}

function PeopleList(props) {
  const arr = props.data;
  const listItems = arr.map((val, index) => <li key={index}>{val}</li>);
  return <ul>{listItems}</ul>;
}
function SearchName(props) {
  const [contacts, setContacts] = useState(props.data);
  const [person, setPerson] = useState("");
  const [isInList, setIsInList] = useState(false);
  const [text, setText] = useState("");
  function handleChange(e) {
    setPerson(e.target.value);
  }
  function handleSubmit(e) {
    setIsInList(false);
    for (var c of contacts) {
      if (c == person) {
        setIsInList(true);
        break;
      }
    }
    if (isInList) {
      setText("is in list");
    } else {
      setText("is not in list");
    }

    e.preventDefault();
  }
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="enter name to search"
        onChange={handleChange}
      />
      <button type="sumbit">Search</button>
      <p>{text}</p>
    </form>
  );
}
function ContactManager(props) {
  const [contacts, setContacts] = useState(props.data);

  function addPerson(name) {
    setContacts([...contacts, name]);
  }
  function removePerson(name) {
    var newContacts = new Array();
    var i = 0;
    for (var c of contacts) {
      if (c != name) {
        newContacts[i] = c;
        i++;
      }
    }
    setContacts(newContacts);
  }
  return (
    <div>
      <AddPersonForm handleSubmit={addPerson} />
      <RemovePersonForm handleSubmit={removePerson} />
      <SearchName data={contacts} />
      <PeopleList data={contacts} />
    </div>
  );
}
const contacts = ["James Smith", "Thomas Anderson", "Bruce Wayne"];

ReactDOM.render(
  <ContactManager data={contacts} />,
  document.getElementById("root"),
);

1

3 Answers 3

1

The root issue is that since setState is asynchronous, isInList hasn't had the time to change by the time you're checking it in handleSubmit.

However, since the printed text is strictly a function of whether isInList is true, it shouldn't be a separate state atom. If the computation was more complex, I'd recommend using useMemo for it.

On a similar note, you shouldn't "fork" the data prop to local contacts state.

Finally, you can simplify the finding procedure to a simple .find call instead of a loop.

function SearchName(props) {
  const [person, setPerson] = useState("");
  const [isInList, setIsInList] = useState(false);
  function handleChange(e) {
    setPerson(e.target.value);
  }
  function handleSubmit(e) {
    setIsInList(props.data.find((c) => c === person));
    e.preventDefault();
  }
  const text = isInList ? "is in list" : "is not in list";
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="enter name to search"
        onChange={handleChange}
      />
      <button type="sumbit">Search</button>
      <p>{text}</p>
    </form>
  );
}
Sign up to request clarification or add additional context in comments.

Comments

0

The problem is here

setIsInList(true);

This is an async method, meaning it will be updated after the whole function is executed. To solve this you can use useMemo method as follows (as expalained by @AKX. useEffect shouldnt be used)

  const searchResult = useMemo(() => {
    if (isInList) {
      return "is in list";
    } else {
      return "is not in list";
    }
  }, [isInList]);

CodeSandbox here

1 Comment

You shouldn't use useEffect for state derived from other state (unless asynchronous); just use useMemo.
0

It's because React states only update after an eventHandler is finished - that's something called batch update, In your case, you need to go "another round" to have it updated.

Here is my suggestion from your code:

Your modified sourcecode

Keep up the good learning!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.