Search code examples
javascriptreactjsreact-hookseslintuse-effect

React Hook useEffect has a missing dependency: updateFunction


I have this code, where I am using useEffect to update my page with the infinite scroll. However while compiling the same, I see this error

src\components\News.js
  Line 40:6:  React Hook useEffect has missing dependencies: 'props.category' and 'updateNews'. Either include them or remove the dependency array  react-hooks/exhaustive-deps

But when I am including this function in the dependency array [page, props.category, updateNews] I am getting another error

Line 17:9:  The 'updateNews' function makes the dependencies of useEffect Hook (at line 40) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of 'updateNews' in its own useCallback() Hook  react-hooks/exhaustive-deps

Here is my code : Whenever user scrolls the page number is updated and the use Effect hook is being called, which in turn is calling the updateNews function.

import React, { useEffect, useState } from "react";
import NewsItem from "./NewsItem";
import Spinner from "./Spinner";
import PropTypes from "prop-types";
import InfiniteScroll from "react-infinite-scroll-component";

const News = (props) => {
  const [articles, setArticles] = useState([]);
  const [loading, setLoading] = useState(true);
  const [page, setPage] = useState(1);
  const [totalResults, setTotalResults] = useState(0);

  const capitalizeFirstLetter = (string) => {
    return string.charAt(0).toUpperCase() + string.slice(1);
  };

  const updateNews = async () => {
    props.setProgress(10);
    let goToPage = page;
    const url = `https://newsapi.org/v2/top-headlines?country=${props.country}&category=${props.category}&apiKey=${props.apiKey}&page=${goToPage}&pageSize=${props.pageSize}`;
    props.setProgress(30);
    let data = await fetch(url);
    props.setProgress(50);
    let parsedData = await data.json();
    props.setProgress(70);
    if (parsedData) {
      setArticles(articles.concat(parsedData.articles));
      setLoading(false);
      setPage(page);
      setTotalResults(parsedData.totalResults);
    }
    props.setProgress(100);
  };

  useEffect(() => {
    updateNews();
    // eslint-disable-next-line
    
    document.title = `${capitalizeFirstLetter(props.category)} - NewsMonkey`;
  }, [page, props.category]);

  const fetchMoreData = async () => {
    setPage(page + 1);
  };

  return (
    <>
      <h3 className="text-center" style={{ marginTop: "4%" }}>
        NewsMonkey - Top {`${capitalizeFirstLetter(props.category)}`} Headlines
      </h3>
      {loading && <Spinner />}
      <InfiniteScroll
        dataLength={articles.length}
        next={fetchMoreData}
        hasMore={articles.length < totalResults}
        loader={<Spinner />}
      >
        <div className="container">
          <div className="row">
            {articles.map((element) => {
              return (
                <div className="col-md-4" key={element.url}>
                  <NewsItem
                    title={
                      element && element.title ? element.title.slice(0, 45) : ""
                    }
                    description={
                      element && element.description
                        ? element.description.slice(0, 50)
                        : ""
                    }
                    imageUrl={element.urlToImage}
                    newsUrl={element.url}
                    author={element.author}
                    date={element.publishedAt}
                    source={element.source.name}
                  />
                </div>
              );
            })}
          </div>
        </div>
      </InfiniteScroll>
    </>
  );
};

export default News;

Note : I tried the answer that I was getting as suggestion to this question but that did not work for me.


Solution

  • Your solution is actually provided inside the error. After including updateNews inside the dependency array, without any memorization on every rerender React will always see updateNews !== updateNews, which is due to the nature of JS (even equally written functions will never be equal to one another).

    Wrapping the updateNews function inside useCallback basically allows React to know that this function will be the same on every rerender until one of its dependency array items change.

    This should work:

    const updateNews = useCallback(async () => {
        props.setProgress(10);
        let goToPage = page;
        const url = `https://newsapi.org/v2/top-headlines?country=${props.country}&category=${props.category}&apiKey=${props.apiKey}&page=${goToPage}&pageSize=${props.pageSize}`;
        props.setProgress(30);
        let data = await fetch(url);
        props.setProgress(50);
        let parsedData = await data.json();
        props.setProgress(70);
        if (parsedData) {
          setArticles(articles.concat(parsedData.articles));
          setLoading(false);
          setPage(page);
          setTotalResults(parsedData.totalResults);
        }
        props.setProgress(100);
      }, [page, props.country, props.category, props.apiKey, props.pageSize]);
    
      useEffect(() => {
        updateNews();
        // eslint-disable-next-line
        
        document.title = `${capitalizeFirstLetter(props.category)} - NewsMonkey`;
      }, [props.category, updateNews]);