Gustė
Gustė

Reputation: 137

Too many re-renders in React

The program should take the input user typed, search the data and return results in a drop down list. When the userinput is more than 3 symbols, the Search() is called and I get "Error: Too many re-renders". Can't find where is the render loop.

import LTCityNames from "../lt-city-names.json"; //JSON object

const Openweathermap = () => {
     const [searchList, setSearcList] = useState([]); //drop down list according to search word
     const [text, setText] = useState(""); //text in the input field
  
     const Search = (userinput) => {
         let correctResult = "";
         let dropdownList = [];

     const regex = new RegExp(`^${userinput}`, "i");
        for (let i = 0; i < LTCityNames.length; i++) {
           correctResult = regex.test(LTCityNames[i].name);
        if (correctResult){
           dropdownList.push(LTCityNames[i]);
           setSearcList(dropdownList);
        }   
      }
  };

     const onChangeInput = (userinput) => {
       setText(userinput);
       if (userinput.length > 2) {
         Search(userinput);
       }
     };

   return (
     <input
      value={text}
      onChange={(e) => {onChangeInput(e.target.value)} }
      type="text"
      placeholder="Enter address"
     ></input>
     <div id="myDropdownWeather" className="dropdown-content">
       {searchList.map((itemInArray) => {
         return (
           <ul>
             <li>{itemInArray.name}</li>
           </ul>
         );
       })
}

Upvotes: 3

Views: 472

Answers (3)

Eb Heravi
Eb Heravi

Reputation: 398

I think you must use useEffect like this:

  const [text, setText] = useState(""); //text in the input field

  const lastFilter = useRef(text);

  useEffect(() => {
    if (lastFilter.current !== text && text.lenght>2) {
        Search(userinput);
        lastFilter.current = text;
    }
}, [text]);

 const onChangeInput = (event) => {
   var userinput=event.target.value;
   setText(userinput);
 };

and change

onChange={(e) => {onChangeInput(e.target.value)} }

to

onChange={(e) => {onChangeInput(e)} } 

Upvotes: 3

Victor Thadeu
Victor Thadeu

Reputation: 107

First: Why you are getting "Error: Too many re-renders"?

When you are using React Functional Components, every time you call a "setState" React reload all your Component, and since you are using functions inside you component these functions are also being loaded every single time your component change. So, when you type your search, the element will re-render uncontrollably.

Solving the problem:

  • Every time you want to use a function inside a React Functional Component you must use React.useCallback because this way you can control exactly when a function should be reloaded in memory preventing the errors you are getting.
  • One more thing, inside your return when you are working with react you cannot return more than one JSX Element, this will also cause you a lot of problems, to solve this you can use the fragment element <> ... </> or any other master element that will hold all the others (fragment elements will not interfere with you CSS).

The Code:

import React, { useCallback, useState } from 'react';
import LTCityNames from '../lt-city-names.json'; // JSON object

const Openweathermap = () => {
  const [searchList, setSearcList] = useState([]); // drop down list according to search word
  const [text, setText] = useState(''); // text in the input field

  const Search = useCallback((userinput) => {
    const correctResult = '';
    const dropdownList = [];

    const regex = new RegExp(`^${userinput}`, 'i');
    for (let i = 0; i < LTCityNames.length; i++) {
      const correctResult = regex.test(LTCityNames[i].name);
      if (correctResult) {
        dropdownList.push(LTCityNames[i]);
        setSearcList(dropdownList);
      }
    }
  }, []);

  const onChangeInput = useCallback(
    (e) => {
      const userinput = e.target.value;
      setText(userinput);
      if (userinput.length > 2) {
        Search(userinput);
      }
    },
    [Search],
  );

  return (
    <> // Fragment element start
      <input
        value={text}
        onChange={(e) => onChangeInput(e)}
        type="text"
        placeholder="Enter address"
      />
      <div id="myDropdownWeather" className="dropdown-content">
        {searchList.map((itemInArray) => {
          return (
            <ul>
              <li>{itemInArray.name}</li>
            </ul>
          );
        })}
      </div>
    </> // Fragment element end
  );
};

Understanding useCallback:

  • useCallback is a React function that will receive 2 parameters the first one is your function and the second one is an array of parameters that when changed will trigger a reload in memory for the function (every time you use an element that came from outside the function itself you need to use it as a parameter to reload the function in memory).

const myReactFunction = useCallback(() => {}, [a,b,c....] )

Improving you Component Return:

  • You are not required to use any of the tips listed bellow but they will improve the readability of your code.

  • Since you are calling your input onChange with (e) => onChangeInput(e) you can change your input to only onChangeInput:

     <input
         value={text}
         onChange={onChangeInput} // same as (e) => function(e) 
         type="text"
         placeholder="Enter address"
     />
    
  • The second tip is inside you map function, since you are using arrow functions you are not required to type return():

     {searchList.map((itemInArray) => (
         <ul>
             <li>{itemInArray.name}</li>
         </ul>
     ))}
    

Upvotes: 3

Vũ Trọng Quang
Vũ Trọng Quang

Reputation: 37

import LTCityNames from "../lt-city-names.json"; //JSON object

const Openweathermap = () => {
     const [searchList, setSearcList] = useState([]); //drop down list according to search word
     const [text, setText] = useState(""); //text in the input field
  
     const Search = (userinput) => {
         let correctResult = "";
         let dropdownList = [];

     const regex = new RegExp(`^${userinput}`, "i");
        for (let i = 0; i < LTCityNames.length; i++) {
           correctResult = regex.test(LTCityNames[i].name);
        if (correctResult){
           dropdownList.push(LTCityNames[i]);
           setSearcList(dropdownList);
        }   
      }
  };

     const onChangeInput = (userinput) => {
       setText(userinput);
       if (userinput.length > 2) {
         Search(userinput);
       }
     };


//remove value={text}
       return (
         <input
          onChange={(e) => {onChangeInput(e.target.value)} }
          type="text"
          placeholder="Enter address"
         ></input>
         <div id="myDropdownWeather" className="dropdown-content">
           {searchList.map((itemInArray) => {
             return (
               <ul>
                 <li>{itemInArray.name}</li>
               </ul>
             );
           })
    }

Remove value = {text}

Upvotes: 0

Related Questions