Home > OS >  Unable to set state in a function
Unable to set state in a function

Time:05-01

So I was trying to build a server side pagination that works like a static one and I'm almost there, But I've encountered some issues which I cannot seem to solve.

This is what my code looks like

const LiveIndex = (props) => {
  const [currentPage, setCurrentPage] = useState(0);
  const [isLoading, setLoading] = useState(false);
  const startLoading = () => setLoading(true);
  const stopLoading = () => setLoading(false);

  useEffect(() => {
    //After the component is mounted set router event handlers

    Router.events.on("routeChangeStart", startLoading);
    Router.events.on("routeChangeComplete", stopLoading);

    return () => {
      Router.events.off("routeChangeStart", startLoading);
      Router.events.off("routeChangeComplete", stopLoading);
    };
  }, []);

  const paginationHandler = (page) => {
    const currentPath = props.router.pathname;
    const currentQuery = props.router.query;
    currentQuery.page = currentPage   1;

    props.router.push({
      pathname: currentPath,
      query: currentQuery,
    });
    setCurrentPage(currentQuery.page);
  };

  const backToLastPage = (page) => {
    const currentPath = props.router.pathname;
    const currentQuery = props.router.query;

    currentQuery.page = currentPage - 1;
    setCurrentPage(currentQuery.page); // THE code that breaks my code.

    props.router.push({
      pathname: currentPathh,
      query: currentQueryy,
    });
  };

  let content;
  if (isLoading) {
    content = (
      <div>
        <h2 >loading.</h2>
      </div>
    );
  } else {
    //Generating posts list

    content = (
      <div className="container">
        <h2> Live Games - </h2>

        <div className="columns is-multiline">
          <p>{props.games.name}</p>
        </div>
      </div>
    );
  }

  return (
    <>
      <div className={"container-md"}>
        <div>{content}</div>

        {props.games.length ? (
          <a onClick={() => paginationHandler(currentPage)}> moore </a>
        ) : (
          backToLastPage(currentPage)
        )}
      </div>
    </>
  );
};

export async function getServerSideProps({ query }) {
  const page = query.page || 1; //if page empty we request the first page
  const response = await fetch(
    `exampleapi.com?sort=&page=${page}&per_page=10&token`
  );

  const data = await response.json();
  return {
    props: {
      games: data,
    },
  };
}

export default withRouter(LiveIndex);

The issue is my backToLastPage does the job well but I'm unable to use setCurrentPage() in that function, Every time I use that I get the following error

Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop

How can I possibly update the value of my currentPage state in the backToLast function

Thank you

CodePudding user response:

You're calling backToLastPage directly in JSX which will be re-rendered/re-called every time. And setCurrentPage (with useState) triggers re-rendering for state changes in backToLastPage.

You can imagine that every time the state changes, your component gets rendered and it will set states again that make infinite renderings for the component.

You can use useEffect to handle props.games changes. That will help you to trigger backToLastPage only once whenever props.games get changed.

React.useEffect(() => {
   if(!props.games || !props.games.length) {
      backToLastPage(currentPage)
   }
},[props.games])

Full modification can be

const LiveIndex = (props) => {
  const [currentPage, setCurrentPage] = useState(0);
  const [isLoading, setLoading] = useState(false);
  const startLoading = () => setLoading(true);
  const stopLoading = () => setLoading(false);

  useEffect(() => {
    //After the component is mounted set router event handlers

    Router.events.on("routeChangeStart", startLoading);
    Router.events.on("routeChangeComplete", stopLoading);

    return () => {
      Router.events.off("routeChangeStart", startLoading);
      Router.events.off("routeChangeComplete", stopLoading);
    };
  }, []);

  //The main change is here
  //It will be triggered whenever `props.games` gets updated
  React.useEffect(() => {
    if(!props.games || !props.games.length) {
      backToLastPage(currentPage)
    }
  },[props.games])

  const paginationHandler = (page) => {
    const currentPath = props.router.pathname;
    const currentQuery = props.router.query;
    currentQuery.page = currentPage   1;

    props.router.push({
      pathname: currentPath,
      query: currentQuery,
    });
    setCurrentPage(currentQuery.page);
  };

  const backToLastPage = (page) => {
    const currentPath = props.router.pathname;
    const currentQuery = props.router.query;

    currentQuery.page = currentPage - 1;
    setCurrentPage(currentQuery.page); // THE code that breaks my code.

    props.router.push({
      pathname: currentPathh,
      query: currentQueryy,
    });
  };

  let content;
  if (isLoading) {
    content = (
      <div>
        <h2 >loading.</h2>
      </div>
    );
  } else {
    //Generating posts list

    content = (
      <div className="container">
        <h2> Live Games - </h2>

        <div className="columns is-multiline">
          <p>{props.games.name}</p>
        </div>
      </div>
    );
  }

  return (
    <>
      <div className={"container-md"}>
        <div>{content}</div>

        {props.games.length && (
          <a onClick={() => paginationHandler(currentPage)}> moore </a>
        )}
      </div>
    </>
  );
};

export async function getServerSideProps({ query }) {
  const page = query.page || 1; //if page empty we request the first page
  const response = await fetch(
    `exampleapi.com?sort=&page=${page}&per_page=10&token`
  );

  const data = await response.json();
  return {
    props: {
      games: data,
    },
  };
}

export default withRouter(LiveIndex);
  • Related