8

I have run into a weird bug with my search input component where it loses focus on every keypress and can't figure out why.

const App = () => {
  let [characters, setCharacters] = useState([]);
  let [filteredCharacters, setFilteredCharacters] = useState([]);
  // more state
  let [search, setSearch] = useState("");

  useEffect(() => {
    setIsLoading(true);
    axios
      .get(`https://swapi.co/api/people/?page=${pageNumber}`)
      .then(res => {
        console.log(res.data.results);
        setCharacters(res.data.results);
        setFilteredCharacters(res.data.results);
      })
      .catch(err => console.error(err))
      .then(() => {
        setIsLoading(false);
      });
  }, [pageNumber]);

  function updatePage(e) {
     // update page
  }

  function handleSearch(e) {
    setSearch(e.target.value);
    const filtered = characters.filter(character => {
      if (character.name.toLocaleLowerCase().indexOf(e.target.value) !== -1) {
        return character;
      }
    });
    setFilteredCharacters(filtered);
  }
  function SearchBar() {
    return (
      <React.Fragment>
        <StyledInput
          type="text"
          id="search"
          value={search}
          placeholder="Search by name..."
          onChange={e => handleSearch(e)}
        />
      </React.Fragment>
    );
  }

  return (
    <React.Fragment>
      <GlobalStyles />
      <div className="App">
        <Heading>React Wars!</Heading>
        <SearchBar />
        <Pagination handleClick={updatePage} />
        {!isLoading && Object.entries(filteredCharacters).length ? (
          <CharacterList characters={filteredCharacters} />
        ) : (
          <LoadingBar />
        )}
      </div>
    </React.Fragment>
  );
};

I'm also getting a Line 65:50: Expected to return a value at the end of arrow function array-callback-return for the characters.filter() line so I don't know if that has to do with it.

If it helps, the deployed app with the bug can be seen here --> https://mundane-vacation.surge.sh

Austin Walela
  • 195
  • 3
  • 18
  • 4
    In React, if an input loses focus when a character is typed, 99% of the time it's because it's getting re-rendered. I'm guessing it's because you've nested functional components inside another and are using them as children. The `SearchBar`'s `onChange` calls `handleSearch`, which calls `setSearch`, which changes the state value and thus causes a re-render (including a re-render of the `SearchBar` child itself). Check out other questions like this one for ideas on how to fix: https://stackoverflow.com/questions/42573017/in-react-es6-why-does-the-input-field-lose-focus-after-typing-a-character – Jayce444 Nov 09 '19 at 11:08
  • you're defining `function SearchBar() {` every render, so it's a completely new component getting evaluated and rendered every time. You can omit that and inject the `StyledInput` contents in place of `` and that should help – Derek Nov 09 '19 at 11:13
  • The warning/error you're getting is because your `characters` filter functor should be returning a boolean value every time, not the current value of the functor. You can simply return the value of that name tolowercase index check. – Derek Nov 09 '19 at 11:17
  • This helped me! Because it was more obvious: https://stackoverflow.com/a/59287017/7156690 – Sandip Mane Feb 21 '20 at 10:52

1 Answers1

8

You have a problem with focus because your component SearchBar is declared inside App component and it is recreated on each rendering. You need to move SearchBar out of App or just move StyledInput on the place of SearchBar. If you choose the first way, I recommend you remove React.Fragment from SearchBar, because it has only one rendered child:

function SearchBar(props) {
    return (
        <StyledInput
          type="text"
          id="search"
          value={props.value}
          placeholder="Search by name..."
          onChange={props.onChange}
        />
    );
  }

And in App:

<SearchBar onChange={handleSearch} value={search} />

To fix your warning, you need always return a boolean value inside the filter function. Now you return nothing (i.e. undefined) in case if the character does not pass the condition. So change your filter function like this:

const filtered = characters.filter(character => {
    return character.name.toLocaleLowerCase().indexOf(e.target.value) !== -1
});
Andrii Golubenko
  • 4,879
  • 1
  • 22
  • 27