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"),
);

AKX
  • 152,115
  • 15
  • 115
  • 172
  • setState is async here is a link for better understanding [setState is asynchronous](https://stackoverflow.com/questions/42593202/why-calling-setstate-method-doesnt-mutate-the-state-immediately/42593250) – Muhammad Bilal Bangash Aug 09 '21 at 13:06

3 Answers3

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>
  );
}
AKX
  • 152,115
  • 15
  • 115
  • 172
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

RABI
  • 692
  • 2
  • 15
  • You shouldn't use `useEffect` for state derived from other state (unless asynchronous); just use `useMemo`. – AKX Aug 09 '21 at 13:10
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!

Ryan Le
  • 7,708
  • 1
  • 13
  • 23