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
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 word use 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 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>
}
...
};
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.
Related
I am done with the API coding and whenever I click the update button it doesn't work and doesn't call the API. When I click I want the Update button to call the API and display the data which is called by id in the respective fields so that I can re edit the stored data. I do not know where to do what please help me.
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { Table, Button } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
export default function Read() {
let navigate = useNavigate();
const [APIData, setAPIData] = useState([]);
useEffect(() => {
axios.get(`http://localhost:5000/emp`)
.then((response) => {
console.log(response.data)
setAPIData(response.data);
})
}, []);
const setData = (data) => {
let { Employee_name, Employee_id, Employee_address, Employee_post } = data;
localStorage.setItem('Employee Name', Employee_name);
localStorage.setItem('Employee ID', Employee_id);
localStorage.setItem('Employee Address', Employee_address);
localStorage.setItem('Employee Position', Employee_post)
}
// const updateData = (Employee_id) => {
// axios.get(`https://localhost:5000/emp/:Employee_id`)
// .then((data) =>{
// console.log(data)
// setAPIData(update.data);
// })
// }
const getData = () => {
axios.get(`http://localhost:5000/emp`)
.then((getData) => {
setAPIData(getData.data);
})
}
const onDelete = (Employee_id) => {
axios.delete(`http://localhost:5000/emp/:Employee_id`)
.then(() => {
getData();
})
}
const Data = () => {
navigate('/')
}
return (
<div>
<Table singleLine>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Employee Name</Table.HeaderCell>
<Table.HeaderCell>Employee ID</Table.HeaderCell>
<Table.HeaderCell>Employee Address</Table.HeaderCell>
<Table.HeaderCell>Employee position</Table.HeaderCell>
<Table.HeaderCell>Update</Table.HeaderCell>
<Table.HeaderCell>Delete</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{APIData.map((data) => {
return (
<Table.Row>
<Table.Cell>{data.Employee_name}</Table.Cell>
<Table.Cell>{data.Employee_id}</Table.Cell>
<Table.Cell>{data.Employee_address}</Table.Cell>
<Table.Cell>{data.Employee_post}</Table.Cell>
<Link to='/update'>
<Table.Cell>
<Button onClick={() => setData(data)}>Update</Button>
</Table.Cell>
</Link>
<Table.Cell>
<Button onClick={() => onDelete(data.Employee_id)}>Delete</Button>
</Table.Cell>
</Table.Row>
)
})}
</Table.Body>
</Table>
<Button onClick={Data} type='submit'>Home</Button>
</div>
)
}
As mentioned in the comments you have a Link wrapped around a button which you should remove.
// <Link to="/update">
<Table.Cell>
<Button onClick={() => setData(data)}>Update</Button>
</Table.Cell>
// </Link>
In the getData function I recommend to change the name of the variable receiving the data to response to not get confused with the function name itself, like you do in the useEffect
const getData = () => {
axios.get(`http://localhost:5000/emp`).then((response) => {
setAPIData(response.data);
});
};
In the onDelete function you are not correctly passing the Employee_id to the url, you should use ${}. The same in the commented updateData function
const onDelete = (Employee_id) => {
axios.delete(`http://localhost:5000/emp/${Employee_id}`).then(() => {
getData();
});
};
today i have a problem with my searchbar.
const [posts, setPosts] = useState(null)
const [searchTerm, setSearchTerm] = useState("")
useEffect(() => {
const loadPosts = async () => {
try {
const post = await getAllPosts()
setPosts(post)
} catch (e) {
alert("Couldn't load posts")
}
}
loadPosts()
}, [])
return (
<div>
<input type={"text"} placeholder="Search..." onChange={event => {
setSearchTerm(event.target.value)
}}/>
</div>
)
}
This is my Searchbar Component. In the Index file, did i gave a props with.
const [posts, setPosts] = useState([])
const [searchTerm, setSearchTerm] = useState("")
useEffect(() => {
const loadPosts = async () => {
try {
const post = await getAllPosts()
setPosts(post)
} catch (e) {
alert("Couldn't load posts")
}
}
loadPosts()
}, [])
return (
<div className={styles.posts}>
<h1>Market-place Valando</h1>
<SearchList title={posts.filter(post => {
if (post.title.toLowerCase().includes(searchTerm.trim().toLowerCase()) && searchTerm.trim() !== "") {
return post.title
}
}).map(titles => {
{
{titles.title}
}
}
)}/>
{
posts.map(post => {
return (
<div key={post.id} className={styles.key}>
<h1>{post.title}</h1>
<Image width={1000} height={1000} src={post.image}/>
<p>Price: {post.price}.-</p>
<p>Description: {post.description}</p>
<Link href={`/posts/${post.id}`} passHref>
<a>Read more</a>
</Link>
</div>
)
})
}
</div>
)
}
I have a db.json file that i connected with an API File. In this Api File i made all the fetch stuff. This shouldnt be the problem. I think the problem is, that the filter doesnt work properly, with the titels.
You are correct, JavaScript filter does not return specific property values, but it returns the top entries of the array, a.k.a posts. So return post.title or return true will yield the same result. However, the problem in your code appears to be that you are not returning anything from the map function. All you need to do is to change it to the following:
.map(post => post.title)
I am working on countries project.
I get information about this when the border buttons are clicked. But when I click the Back button, the previous country data does not appear. How can I fix this? Please help me!
Here is my Country Component
import React, { useEffect, useState } from 'react';
import { Link, useParams, useNavigate } from 'react-router-dom';
import Loading from './Loading';
function Country() {
const { countryCode } = useParams();
const navigate = useNavigate();
const [country, setCountry] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => { getSingleCountryData(countryCode); }, []);
const getSingleCountryData = async (countryCode) => {
setLoading(true);
try {
const country = JSON.parse(localStorage.getItem("countries")).filter(c => c.cca3 === countryCode);
setCountry(country[0]);
setLoading(false);
} catch (err) {
console.log(err);
}
}
return loading ? <Loading />
: (
<div className='country container'>
<button className='btn backBtn' onClick={() => navigate(-1)}> Back </button>
// some code
<div className="country__borders">
<h4>Border Countries:</h4>
{country.borders && country.borders.map((border, index) => {
return <Link to={`/countries/${border}`} onClick={() => getSingleCountryData(border)} key={index} className='btn'>{border}</Link>
})}
</div>
</div>
);
}
export default Country;
The useEffect hook is missing the getSingleCountryData and countryCode as dependencies. You'll want to memoize the getSingleCountryData callback so it's provided to the useEffect hook as a stable callback reference.
const getSingleCountryData = useCallback(async (countryCode) => {
setLoading(true);
try {
const country = (JSON.parse(localStorage.getItem("countries")) || [])
.filter(c => c.cca3 === countryCode);
setCountry(country[0]);
} catch (err) {
console.log(err);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
getSingleCountryData(countryCode);
}, [countryCode, getSingleCountryData]);
Since the country data is fetched and loaded when the countryCode route param updates there'll no longer be a need to trigger this fetching when the link is clicked.
{country.borders && country.borders.map((border, index) => (
<Link
key={index}
to={`/countries/${border}`}
className='btn'
>
{border}
</Link>
))}
And since the data is not longer fetched via a click handler and only referenced in the useEffect, getSingleCountryData can be moved into the useEffect hook and be removed entirely as a dependency.
const getSingleCountryData = useCallback(, []);
useEffect(() => {
const getSingleCountryData = async (countryCode) => {
setLoading(true);
try {
const country = (JSON.parse(localStorage.getItem("countries")) || [])
.filter(c => c.cca3 === countryCode);
setCountry(country[0]);
} catch (err) {
console.log(err);
} finally {
setLoading(false);
}
}
getSingleCountryData(countryCode);
}, [countryCode]);
DISCLAIMERI'm relatively new to coding so sorry if this is a simple answer!
My problem:
I'm trying to render a modal which sends contact information to my PostgreSQL database with a onclick.
The modal should pick out a role_id from the block its associated too however when i pass it down the tree its console logging as undefined.
Any idea where I'm going wrong, have sent ages trying to figure it out but need some constructive help!!!
This is the parent component:
*please note, ListedContracts is imported elsewhere but working as required.
import React, { Fragment, useEffect, useState } from "react";
import ApplyModal from './ApplyModal'
const ListContracts = () => {
const [contracts, setContracts] = useState([]);
//lists the contract
const getContracts = async () => {
try {
const res = await fetch("http://localhost:5000/freelancedashboard/contracts");
const jsonData = await res.json();
setContracts(jsonData);
} catch (err) {
console.error(err.message);
}
};
useEffect(() => {
getContracts();
}, []);
const renderTable = () => {
return contracts.map((contract) => {
const { user_id, role_id, title, industry, skills, discription } = contract //destructuring
return (
<tr key={role_id}>
<td>{user_id}</td>
<td>{title}</td>
<td>{industry}</td>
<td>{skills}</td>
<td>{discription}</td>
<td>{role_id}</td>
<td>
<ApplyModal role_id = {role_id} />
</td>
</tr>
)
})
};
return (
<Fragment>
<table class="table mt-5 text-center" >
<thead>
<tr>
<th>User</th>
<th>Job Role</th>
<th>Idustry</th>
<th>Skills Required</th>
<th>Brief</th>
</tr>
</thead>
<tbody>
{renderTable()}
</tbody>
</table>
</Fragment>
);
};
export default ListContracts;
Which then I'm using the imported child component ApplyModal in the render statement of parentListContracts and passing down role_id in as a data property...
ApplyModal looks like:
import React, {useState} from "react";
import { Modal, Button } from 'react-bootstrap';
import Apply from "./Apply";
function ApplyModal(role_id) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<>
<Button variant="primary" onClick={handleShow}>
Apply
</Button>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Send Your Info</Modal.Title>
</Modal.Header>
<Modal.Body>
<Apply role_id = {role_id} />
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
}
export default ApplyModal;
Which is originally taking in the role_id from ListContract and then passing it too Apply as role_id's final destination.
Apply
import React, {useState, Fragment, useEffect} from "react";
const Apply = (role_id) => {
const [state, setState] = useState({
name: "",
email: "",
});
const { name, email } = state;
//DISPLAY DATA
const applyMinor = async () => {
try {
//recieves data from server
const res = await fetch("http://localhost:5000/freelancedashboard/freelanceprofile", {
method: "POST",
headers: { token: localStorage.token }
});
const parseData = await res.json();
const name = await parseData.user_name;
const email = await parseData.user_email;
setState({name: name, email: email})
} catch (err) {
console.error(err.message);
}
}
//SENDS DATA
const [role, setRole] = useState({role: ''})
const sendData = async role_id => {
setRole({role: role_id})
try {
const body = { role, name, email };
await fetch(`http://localhost:5000/application/${role}`,
{
method: "POST",
headers: {
token: localStorage.token
},
body: JSON.stringify(body)
}
);
console.log(role, name);
} catch (err) {
console.error(err.message);
}
}
useEffect(() => {
applyMinor();
}, []);
return (
<Fragment>
<div>
<div>
<h1>Name: {name}</h1>
<h1>Email: {email}</h1>
<button onClick={() => sendData()}>Send</button>
</div>
<div>
</div>
</div>
</Fragment>
)
};
export default Apply;
However as previously mentioned, when i console log it as in Apply above the content doesn't console.log(role);.
What am i doing wrong, I know I'm new and naive but any help would go a long way (towards my learning)?
Thanks team!!!
The argument of a functional component is a single object that contains all of the props that are passed with the component.
So in your ApplyModal and Apply components what you're actually accessing when you try to read role_id is the entire props object. Try deserialising it like this:
function ApplyModal({ role_id }) {
...
}
const Apply = ({ role_id }) => {
...
}
As a sidenote - React doesn't use html, it uses something extremely similar called jsx. It's almost identical but there are a few differences like className replaces class. So you need to change where it says <table class="table mt-5 text-center" > to <table className="table mt-5 text-center" >
I want to design an ApiFetcher in order to prevent duplicate code.
How to I pass a custom child component to my ApiFetcher so that it renders the content I specify in the custom component instead of a hard coded element in the return statement?
I want to keep the logic of the ApiFetcher and the CompanyProfile components out of their parent components.
import React from "react";
import ReactDOM from "react-dom";
import { useState, useEffect } from "react";
function ApiFetcher(props) {
const apiUrl =
"https://financialmodelingprep.com/api/v3/profile/AAPL?apikey=demo";
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch(apiUrl)
.then((res) => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
},
(error) => {
setIsLoaded(true);
setError(error);
}
);
}, []);
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
// TODO: move the following <div> out of this component and render children
return (
<div>
{items.map((item) => (
<li key={item.symbol}>
{item.companyName} {item.price}
</li>
))}
</div>
);
}
}
function CompanyProfile() {
return (
<div>
<ApiFetcher>
{/*
TODO: move the following into this component:
<div>
{items.map((item) => (
<li key={item.symbol}>
{item.companyName} {item.price}
</li>
))}
</div>
*/}
</ApiFetcher>
</div>
);
}
ReactDOM.render(<CompanyProfile />, document.getElementById("root"));
You can maybe use props.children in order to move rendering logic to Parent component. Here's how I will strucutre the component:
function ApiFetcher({url}) {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
},
(error) => {
setIsLoaded(true);
setError(error);
}
);
}, []);
if (error) {
return <div>Error: {error.message}</div>;
}
if (!isLoaded) {
return <div>Loading...</div>;
}
return props.children({response: items});
}
Usage
<ApiFetcher>
{({response}) => // Rendering logic}
</ApiFetcher>