Home > Software engineering >  Exporting Async API call to another file
Exporting Async API call to another file

Time:03-08

Im'trying to seperate api call from return/rendering but when importing teams (which contains data) shows as undefined. Is it because of the async await function? How can I export the data and map it in the return on Teams.js? Is there a better way to seperate the Api call from the return/rendering component?

FormTeams.js

import { useState, useEffect } from 'react';
import { Spinner } from "react-bootstrap";
import 'bootstrap/dist/css/bootstrap.min.css';

const FormTeams = () => {
    const BASE_URL = process.env.REACT_APP_URL

    const [isLoading, setIsLoading] = useState(true);
    const [teams, setTeams] = useState([]);

    useEffect(() => {
        getTeams();
    }, []);

    const getTeams = async () => {
        try {
            const response = await fetch(`${BASE_URL}/teams`)
            return response.json()
                .then(data => {
                    setTeams(data)
                    setIsLoading(false)
                })
        } catch (error) {
            console.log(error)
        }
    }

    if (isLoading) {
        return (<Spinner animation="border" variant="primary" />)
    }

    const deleteTeam = async (id) => {
        try {
            await fetch(`${BASE_URL}/teams/${id}`, {
                method: "DELETE",
            }).then(response => {
                setTeams(teams.filter(team => team.id !== id))
                return response.json()
            })
        } catch (error) {
            console.log(error)
        }
    }
    
    return {teams, deleteTeam}
}

export default FormTeams

Teams.js

import { Link, } from "react-router-dom";
import { Button, ButtonGroup } from "react-bootstrap";
import FormTeams from './FormTeams'

const Teams = () => {
   const {teams, deleteTeam} =FormTeams()

    return (
        <div>
            <h2 className='centered'>Clubs</h2>
            <div><Link to="/Teams/add" className="link">Add New</Link></div>
            <table className='teams'>
                <thead>
                    <tr >
                        <th>№</th>
                        <th>Team</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody >
                    {teams.map((team, index) => (
                        <tr key={team.id}>
                            <td>{index   1}.</td>
                            <td>{team.team_name}</td>
                            <td>
                                <ButtonGroup>
                                    <Link to={`/Teams/${team.id}`} className='link'>View</Link>
                                    <Link to={`/Teams/edit/${team.id}`} className='edit'>Edit</Link>
                                    <Button variant="danger" onClick={() => deleteTeam(team.id)}>Delete</Button>
                                </ButtonGroup>
                            </td>
                        </tr>
                    ))}
                </tbody>
            </table>
            <Link to={'/'} className='link'>Back To Home Page</Link>
        </div>
    )
}

export default Teams

CodePudding user response:

Yes. This is where useHooks to the rescue!

  1. First, separate all the logics into a hook
const useTeams = () => {
    const BASE_URL = process.env.REACT_APP_URL

    const [isLoading, setIsLoading] = useState(true);
    const [teams, setTeams] = useState([]);

    const getTeams = async () => {
        try {
            const response = await fetch(`${BASE_URL}/teams`)
            return response.json()
                .then(data => {
                    setTeams(data)
                    setIsLoading(false)
                })
        } catch (error) {
            console.log(error)
        }
    }

    useEffect(() => {
        getTeams();
    }, []);

    const deleteTeam = (id) => {
        fetch(`${BASE_URL}/teams/${id}`, {
            method: "DELETE",
        }).then(response => {
            setTeams(teams => teams.filter(team => team.id !== id))
        }).catch(error => {
            console.log(error)
        }
    }

    return { teams, deleteTeam, isLoading }
}
  1. Then use the returned values as props for the return elements
const Teams = () => {
    const { teams, deleteTeam, isLoading } = useTeams()
    
    if (isLoading) {
        return <Spinner animation="border" variant="primary" />
    }

    return (
        <div>
            <h2 className='centered'>Clubs</h2>
            <div><Link to="/Teams/add" className="link">Add New</Link></div>
            <table className='teams'>
                <thead>
                    <tr >
                        <th>№</th>
                        <th>Team</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody >
                    {teams.map((team, index) => (
                        <tr key={team.id}>
                            <td>{index   1}.</td>
                            <td>{team.team_name}</td>
                            <td>
                                <ButtonGroup>
                                    <Link to={`/Teams/${team.id}`} className='link'>View</Link>
                                    <Link to={`/Teams/edit/${team.id}`} className='edit'>Edit</Link>
                                    <Button variant="danger" onClick={() => deleteTeam(team.id)}>Delete</Button>
                                </ButtonGroup>
                            </td>
                        </tr>
                    ))}
                </tbody>
            </table>
            <Link to="/" className="link">Back To Home Page</Link>
        </div>
    )
}

IMO, The core idea of react hooks is just to separate logics from rendering. And this is the perfect use case for your problem. The hooks provide as a blackbox of logics that may composed of multiple other hooks and can mainly used for providing data or do some behavior.

CodePudding user response:

You're kind of close. There are a couple things you need to change:

  1. As a matter of convention, components (anything which renders content) should always have a PascalCase name (TeamsTable), and hooks should have a name which begins with the word use and is camelCased: useFormData.

  2. Another matter of convention: write your effect code all in one block:

    useEffect(async () => {
        try { ... }
        catch(error) { ... }
    }, []);
    
  3. Make sure your effect declares the right dependencies. In your case, you are referencing the BASE_URL variable. And even though you know the value will never change, you still need to list it as a dependency for your effect:

    useEffect(..., [BASE_URL]);
    
  4. Hooks should not render anything. So move your loader into the TeamsTable component.

  5. You also need to make sure that you set isLoading back to false if there is an error. I highly recommend keeping "error" state too and updating it inside all catch blocks:

    const [error, setError] = useState(null);
    ...
    try { ... }
    catch(error) {
        setError(error);
        setIsLoading(false);
    }
    ...
    

Wrapping it all together (only showing relevant changes)

You should have a new useFormTeams hook instead of the FormTeams function you have now, and the useEffect call should be updated per my suggestions. You should also return the isLoading state:

const useFormTeams = () => {
    const [error, setError] = useState(null);
    ...
    useEffect(async () => {
        try {
            // do async stuff
            setError(null);
            setIsLoading(false);
        }
        catch(error) {
            setError(error);
            setIsLoading(false);
        }
    }, [BASE_URL]
    ...

    return { teams, error, deleteTeam, isLoading };
};

And you will use your new hook as follows:

const TeamsTable = () => {
    const { teams, error, deleteTeams, isLoading } = useFormTeams();
    ...

    if (isLoading) {
        return <Spinner ... />
    }
    if (error) {
        return <div>There was an error: {error}<div>
    }
    ...
};
  • Related