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!
- 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 }
}
- 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:
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 worduse
and is camelCased:useFormData
.Another matter of convention: write your effect code all in one block:
useEffect(async () => { try { ... } catch(error) { ... } }, []);
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]);
Hooks should not render anything. So move your loader into the
TeamsTable
component.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 allcatch
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>
}
...
};