Reputation: 121
My goal is to make an API request when user types something on input. I'm getting the data successfully. However the component is rerendering twice and giving me this warning. If I include 'context' I'm getting an infinite loop. Here's my code:
Component.js:
const SearchBox = () => {
const [searchTerm, setSearchTerm] = useState("");
const { handleSearch, searchResults } = useContext(MovieContext);
console.log(searchResults);
useEffect(() => {
let timer;
timer = setTimeout(() => {
handleSearch(searchTerm);
}, 500);
return () => clearTimeout(timer);
}, [searchTerm]);
const renderResults = () => {
if (searchResults.length > 0) {
searchResults.map(result => {
return (
<div key={result.Title}>
<img src={result.Poster} alt={result.Title} />;
</div>
);
});
}
return;
};
return (
<>
<label>
<b>Search</b>
</label>
<input
className="input"
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
<div className="dropdown is-active">
<div className="dropdown-menu">
<div className="dropdown-content results">{renderResults()}</div>
</div>
</div>
</>
);
};
On top of this context.searchResults is undefined, although I set the initial value as an empty array. I wanted to know what causing this. What am I doing wrong? Here is my context code below:
Context.js:
const Context = React.createContext("");
export class MovieStore extends Component {
constructor(props) {
super(props);
this.state = {
searchResults: [],
handleSearch: this.handleSearch
};
}
handleSearch = async term => {
try {
if (term !== "") {
const response = await axios.get("http://www.omdbapi.com/", {
params: {
apikey: apikey,
s: term
}
});
this.setState({ searchResults: response.data.Search });
}
} catch (error) {
console.log(error);
}
};
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}
Upvotes: 0
Views: 1847
Reputation: 2036
Exactly the same thing about an infinite loop is mentioned in React
docs here. So the cause of the infinite loop is that, in the context render function, you create new value every time render is called.
render() {
return (
<Context.Provider
// ! value object creates every time render is called - it's bad
value={{ ...this.state, handleSearch: this.handleSearch }}
>
{this.props.children}
</Context.Provider>
);
}
It causes every consumer to rerender when context state updates. So, if you put context
in dependencies array of useEffect
, eventually, it'll cause an infinite loop, because context
value is always different. Here's what happens:
Context makes a search query.
Context state updates with the new data, which causes all consumers to rerender.
In context consumer useEffect
sees that context value has been
updated and it calls setTimeout
which will call for another search
in context provider in 500ms.
Consumer calls context to make another search query and we've got an infinite loop!
The solution is to keep context value of the same object, while only updating its properties. It can be done by putting all the necessary properties inside of context state. Like that:
export class MovieStore extends Component {
handleSearch = async term => {
try {
if (term !== "") {
const response = await axios.get("http://www.omdbapi.com/", {
params: {
apikey: "15bfc1e3",
s: term
}
});
this.setState({ searchResults: response.data.Search });
}
} catch (error) {
console.log(error);
}
};
state = {
searchResults: [],
handleSearch: this.handleSearch // <~ put method directly to the state
};
render() {
return (
<Context.Provider value={this.state}> // <~ Just returning state here
{this.props.children}
</Context.Provider>
);
}
}
Upvotes: 2