Home > Mobile >  React app re-rendering and infinitely fetching data using axios
React app re-rendering and infinitely fetching data using axios

Time:04-29

I'm a self-taught developer, almost entirely in native iOS apps. For a project of mine, I've hired a web developer to build a React web app (hosted by Firebase). He's from Ukraine and suddenly had to stop, and so I've taken over to finish it out. Because of this, I haven't been able to learn React/HTTPS/axios/Node.js traditionally, i.e. at a slow pace with tutorials, exercises, core concepts etc. Though a few months ago, I was able to finish it, and everything worked fine. However, in the past few weeks, I've had to restructure my entire architecture including creating a new GCP project, a new Firebase project, new git repositories, etc., which includes all the reconfiguration in addition to some code optimizations and data model adjustments. It is at some point in this restructuring my issue appeared. I mention all this to point out A) I have been heavily dependent on his original work, especially with setting up Firebase Hosting, and B) I'm not exactly sure where the issue is. I'm 90% sure its with React, but it's strange because I didn't really make any changes to the async networking calls from when I completed it two months ago and it worked as intended.

Anyways, on appear, the web app requests which view to present to the client, which is either NotFoundView, a ReviewUserView, or UserProfileView, determined by the backend given a userId. The problem is when UserProfileView is displayed, something causes this view to re-render infinitely. At first, for no more than two seconds, it displays this view correctly, right before resetting and re-rendering very rapidly. I think it has something to do with a useEffect and/or useState React hook, though I'm not sure in which view.

Any help is greatly appreciated, thanks.

export enum ViewState { 
    UserProfile = 'UserProfile',
    ReviewForm = 'ReviewForm',
    NotFound = 'NotFound'
}

....................................................

export class ApiHandler { 
    // Returns a ViewState enum case for main view routing
    public static requestViewState(id: string): Promise<AxiosResponse<ViewState>> { 
        console.log('REQUESTING VIEW STATE')
        return axios.get(`${API_URL}/user/${id}/view-state`)
    }

    // Returns the requested user's profile data   other info
    public static requestUserData(id: string): Promise<AxiosResponse<UserData>> { 
        console.log('REQUESTING USER DATA')
        return axios.get(`${API_URL}/user/${id}`)
    }

    // More API calls ...
}

....................................................

export function RoutingView(props: RouteComponentProps<RoutingViewProps>) { 
    const userId = props.match.params.id
    const [viewState, setViewState] = useState<ViewState>()
    const [showError, setShowError] = useState<boolean>()
    const [showLoader, setShowLoader] = useState<boolean>(true)

    useEffect(() => { 
        loadViewState()
    }, [])

    if (showLoader) { 
        return <PageLoader />
    }

    if (showError) { 
        return <FailedToLoad />
    }

    switch (viewState) { 
        case ViewState.UserProfile:
            return <UserProfileView id={userId} />
        case ViewState.ReviewForm:
            return <ReviewUserView id={userId} />
        default:
            return <NotFoundView />
    }

    async function loadViewState(): Promise<void> { 
        setShowLoader(true)
        try { 
            const viewStateData = await ApiHandler.requestViewState(userId)
            setViewState(viewStateData.data)
        } catch (error) { 
            console.log(error)
            setShowError(true)
        }
        setShowLoader(false)
    }
}

....................................................

export default function UserProfileView(props: UserProfileProps) { 
    console.log('INITIALIZED USERPROFILEVIEW')
    const userId = props.id
    const [userData, setUserData] = useState<UserData>()
    const [showLoader, setShowLoader] = useState<boolean>(false)
    const [service, setService] = useState<Service | null>()
    const [loadFailed, setLoadFailed] = useState<boolean>()

    useEffect(() => {
        if (userData?.user) { 
            const user = userData?.user
            document.title = `${user?.firstName} ${user?.lastName}`
        }
    }, [userData])

    useEffect(() => { 
        loadUserData()
    }, [userData])

    if (loadFailed) { 
        return <FailedToLoad />
    }

    return <div>
        {showLoader ? <PageLoader/> : ''}

        { 
            userData?.user && <div className={service ? styles.Hidden : ''}>
                <UserInfo 
                    user={userData?.user}
                    services={userData?.services}
                    selectedService={(service) => setService(service)}
                    submitted={userData?.hasSubmitted}
                />
            </div>
        }

    // More view components ...

    </div>

    async function loadUserData(): Promise<void> { 
        setShowLoader(true)
        try {
            const res = await ApiHandler.requestUserData(userId)
            setUserData(res.data as UserData)
        } catch (error) { 
            console.log(error)
            setLoadFailed(true)
        }
        setShowLoader(false)
    }
}

When debugging locally, this is what the console prints out in rapid fire. The repeating pattern might be indicative of something though I don't know what. Note: I've renamed some things in this post for simplicity, ReferralRequest.view is actually UserProfileView.

enter image description here

CodePudding user response:

Instead doing this:

//After every rendering ONLY IF `prop` or `state` changes in this case `userDate always change`
useEffect(() => { 
 loadUserData()
}, [userData])

do this:

//Runs ONCE after initial rendering
useEffect(() => { 
 loadUserData()
}, [])

To say why of this you should know the dependencies argument of useEffect(callback, dependencies).

Here the difference case of use

1-) Not provided dependencies:

the side-effect runs after every rendering.

example:

 useEffect(() => {
    // Runs after EVERY rendering
  });  

2-) An empty array []: This is what you should do.

The side-effect runs once after the initial rendering.

example:

 useEffect(() => {
    // Runs ONCE after initial rendering
  }, []);

3-) Has props or state values: this is your problem.

The side-effect runs only when any depenendecy value changes.

example:

useEffect(() => {
    // Runs ONCE after initial rendering
    // and after every rendering ONLY IF `prop` or `state` changes
  }, [prop, state]);

CodePudding user response:

Change the code

 useEffect(() => { 
    loadUserData()
}, [userData])

To

 useEffect(() => { 
    if (!userData) loadUserData()
}, [userData])

fixes the infinite fetching, useEffect method is dependent on any changes in userData. Every fetch will then change that variable, resulting in infinite loop. Adding a if null check will stop another data fetch.

CodePudding user response:

This is because of this pice of code

useEffect(() => { 
    loadUserData()
}, [userData])

The thing is that the second argument of the useEffect is the variables that the effect depends on. If any of these variables are changed the call-back in the useEffect is called again. And your code generate infinite loop by loading this data, seeing them with the setUserData, data changed and loadUserData called again and so on

  • Related