unes savari
unes savari

Reputation: 11

Promise data when using react and axios

in this code whenever I try to direct to /dashboard it wouldn't wait for the response of axios and goes immediately to return part and it use loggedin with it's default value which is undefined here. Well I guess I should use promises but I don't know how... So I would appreciate it if someone could help.

import axios from 'axios';
import React, { useEffect, useRef, useState } from 'react';
import { Redirect } from 'react-router';
import OverallCondition from "./dashOverall";
import Toolbar from "./dashToolbar";

export default function Dashboard(){
    const [loggedin, check] = useState()
    axios.get('/loggedin')
        .then(response => {
            check(response.data)
        })
        .catch(err => console.error(err));
    return <section className='dashboard'>
            {loggedin ? <div>
                <Toolbar/>
                <OverallCondition/>
            </div> : <Redirect to='/login'/>}
    </section>
}```

Upvotes: 0

Views: 1471

Answers (2)

Yousaf
Yousaf

Reputation: 29282

You need to use useEffect hook to make the HTTP request.

Making the HTTP request at the top-level inside your function component will trigger a new HTTP request every time your component is re-rendered.

Also, since the axios.get(...) is asynchronous, code below the axios.get(...) will execute before the request is completed.

To handle this situation appropriately, follow the steps mentioned below:

  1. Create a state that represents whether the HTTP request is pending or not. Its initial value should be true

    const [isPending, setIsPending] = useState(true);
    
  2. Use the useEffect hook to make the HTTP request. Making HTTP requests at the top-level inside the function component is NOT the right approach - you don't want to make a request every time your component re-renders

    Also don't forget to set isPending to false, otherwise user will continue to see the loading spinner even after the request has completed. You can use the finally() method to call setIsPending(false)

    useEffect(() => {
       axios.get('/loggedin')
        .then(response => setLoggedIn(response.data))
        .catch(err => console.error(err))
        .finally(() => setIsPending(false));
    }, []);
    

    Empty array passes as a second argument to the useEffect hook will ensure that the HTTP request is initiated only once, after the initial render of the component

  3. While the request is pending, show some loading spinner to the user. When the component first renders, as the isPending is true, user will see the loading spinner

    if (isPending) {
       return <Spinner/>;  // you need to create the "Spinner" component
    }
    

Here's how you could implement the above steps in your code:

function Dashboard() {
    const [loggedin, setLoggedIn] = useState();

    // this state will be used to show a loading spinner
    const [isPending, setIsPending] = useState(true);

    // for HTTP request, use the "useEffect" hook
    useEffect(() => {
       axios.get('/loggedin')
        .then(response => setLoggedIn(response.data))
        .catch(err => console.error(err))
        .finally(() => setIsPending(false));
    }, []);

    // show a spinner to the user while HTTP 
    // request is pending
    if (isPending) {
       return <Spinner/>;  
    }
    
    return (
       <section className='dashboard'>
          {loggedin ? (
                <div>
                  <Toolbar/>
                  <OverallCondition/>
                </div> 
              ) : <Redirect to='/login'/>
           }
       </section>
     );
}

Upvotes: 1

iunfixit
iunfixit

Reputation: 994

Issues with the code

Use a better terminology with the useState return

Instead of

const [loggedin, check] = useState()

Use

const [loggedin, setLoggedin] = useState()

Understand the life cycle

Your axios.get is inside the function body, all code there will be executed on every render, it is certainly not what you wanted, for operations that may have side effects, you need to use the useEffect hook https://reactjs.org/docs/hooks-effect.html

The useEffect hook allows you to control better when the code will execute, in your case, you want it to run once

const ShowOnceLoaded = ({ isLoaded, children }) => {
   if (!isLoaded)
     return <p>Loading...</p>

   return <>{children}</>
}

export default function Dashboard(){
    const [ isLoaded, setIsLoaded ] = useState(false)
    const [loggedin, setLoggedin] = useState(false)

    React.useEffect(() => {
      axios.get('/loggedin')
        .then(response => {
            setLoggedin(true)
            setIsLoaded(true)
        })
        .catch(err => console.error(err));
    }, [])
    
    return <section className='dashboard'>
            <ShowOnceLoaded isLoaded={isLoaded}>
            {loggedin ? <div>
                <Toolbar/>
                <OverallCondition/>
            </div> : <Redirect to='/login'/>}
            </ShowOnceLoaded>
    </section>
}

In addition to what you had, now there is a state set once the request is complete, only then we can decide if the user is logged in or not, otherwise, we will redirect the user even before the request is done, as the state initializes as false by default

The empty array on useEffect is used to run the code only when the component mounts

Upvotes: 0

Related Questions