kertAW
kertAW

Reputation: 263

React-component loops

There is a react-component:

import React, { useState, useCallback } from 'react';
import { useHttp } from '../../hooks/http.hooks';

function Main() {
    const {loading, error, request} = useHttp();
    const [news, setNews] = useState(0);

    const topNews = useCallback(async function() {
        const data = await request('http://localhost:5500/api/news/top/2');
        return data;
    }, []);

    console.log(topNews());

    return (
        <div>Hello world</div>
    );
}

export default Main;

And a custom hook:

import { useState } from 'react';

export const useHttp = () => {
    const [loading, setLoading] = useState();
    const [error, setError] = useState();
    async function request(url, { method = 'GET', body = null, headers = {} } = {}) {
        setLoading(true);
        try {
            const response = await fetch(url, { method, body, headers });
            const data = await response.json();

            if (!response.ok) {
                throw new Error(data.msg || 'unhandled error');
            }

            return data;
        } catch (err) {
            setError(err);
        } finally {
            setLoading(false);
        }
    }

    return { loading, request, error }
}

Starting it throw error:

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

And so many Promises in console:

enter image description here

As I understood, when loading is changing Main is rendering, because I added a useCallback() but it's not working. How to get rid of looping right?

Upvotes: 0

Views: 70

Answers (2)

Francis Chartrand
Francis Chartrand

Reputation: 167

Inside your React Component, request should be called inside useEffect instead of useCallback. If you keep it that way, the loop looks like this :

  1. Component render
  2. request is called
  3. request update component states
  4. states change force render, so go back to bullet #2

You should change your code to something like this :

import React, { useState, useEffect } from 'react';
import { useHttp } from '../../hooks/http.hooks';

function Main() {
    const {loading, error, request} = useHttp();
    const [news, setNews] = useState([]);

    useEffect(() => {
      const data = await request('http://localhost:5500/api/news/top/2');
      setNews(data);
    }, [])

    console.log(news);

    return (
        <div>Hello world</div>
    );
}

export default Main;

See useEffect for more details and advanced usage.

Upvotes: 3

Dennis Vash
Dennis Vash

Reputation: 53874

Move the async function call to useEffect, you currently call it on every render:

function Main() {
    const {loading, error, request} = useHttp();
    const [news, setNews] = useState(0);


    useEffect(() => {
      async function topNews() {
        const data = await request('http://localhost:5500/api/news/top/2');
        return data;
    }
      setNews(topNews());
    }, [])

    return (
        <div>{JSON.stringify(news,null,2)}</div>
    );
}

Upvotes: 1

Related Questions