I'm trying to use the github api to get a user's projects and list them in a popup window. I'm having trouble figuring out why async / await isn't working and the data i end up passing is always undefined.
This is how i fetch the data from the api (edited to use for... of):
export default async function GitHubFetch({ userName }) {
let returnArray = [];
let response = await customFetch(
Urls.GitHub + "users/" + userName + "/repos"
);
for (const element of response) {
let project = {};
project.name = element.name;
project.description = element.description;
project.html_url = element.html_url;
let langResponse = await customFetch(element.languages_url);
project.languages = Object.keys(langResponse);
returnArray.push(project);
}
console.log("the array i'm returning from fetch is: ", returnArray);
return returnArray;
}
the console.log of returnArray from this function is:
[{"name":"cthulu_finance","description":"stock-trading application written in react and node.js / express","html_url":"https://github.com/contip/cthulu_finance","languages":["TypeScript","HTML","CSS"]},{"name":"c_structures","description":"collection of data structures in c","html_url":"https://github.com/contip/c_structures","languages":["C"]},{"name":"masm_io_procedures","description":"Low-level implementations of string-to-int and int-to-string in x86 assembly","html_url":"https://github.com/contip/masm_io_procedures","languages":["Assembly"]}]
the array of projects from the above function is used to generate the list of projects by this:
export default function GitHubListDisplay({ projects }) {
let listItems = [];
console.log(projects);
if (Array.isArray(projects)) {
projects.forEach((project, index) => {
listItems.push(
<>
<ListGroup.Item action href={project.html_url}>
{project.name}
</ListGroup.Item>
<ListGroup.Item>{project.description}</ListGroup.Item>
<ListGroup horizontal>{HorizontalList(project.languages)}</ListGroup>
</>
);
});
}
return <ListGroup>{listItems}</ListGroup>;
}
and finally, it's all controlled by this function:
export default function GitHubPopUp({ userName }) {
const [projectData, setProjectData] = useState([]);
useEffect(() => {
async function fetchData() {
setProjectData(await GitHubFetch({ userName }));
console.log("the project data i fetched is: ", projectData);
}
fetchData();
}, []);
return (
<>
<OverlayTrigger
placement="right"
delay={{ show: 250, hide: 5000 }}
overlay={
<Popover>
<Popover.Title as="h3">{`GitHub Projects`}</Popover.Title>
<Popover.Content>
<strong>{userName}'s GitHub Projects:</strong>
{projectData.length > 0 && (
<GitHubListDisplay {...projectData} />
)}
</Popover.Content>
</Popover>
}
>
<Button variant="link">{userName}</Button>
</OverlayTrigger>
</>
);
}
From the main controller function, the state eventually gets set correctly, but if i console.log the projectData state directly after awaiting the Fetch function result, it's undefined.. result is:
the project data i fetched is: []
Additionally, even though i have
{projectData.length > 0 &&
before rendering the GitHubListDisplay component, console.logging the input projects property always results in undefined. The function never ends up displaying anything.
Can anyone please help? I've spent an embarrassing number of hours trying to figure this out.
You cannot use forEach indeed for async await as you desire. Just use a modern for … of loop instead, in which await will work as you expected.
Refer to here
However, best practice is to use Promise.all
export default function GitHubPopUp({ userName }) {
const [projectData, setProjectData] = useState([]);
useEffect(() => {
async function fetchData() {
setProjectData(await GitHubFetch({ userName }));
}
fetchData();
}, []);
useEffect(() => {
console.log("the project data i fetched is: ", projectData);
}, [ projectData ]);
return (
<>
<OverlayTrigger
placement="right"
delay={{ show: 250, hide: 5000 }}
overlay={
<Popover>
<Popover.Title as="h3">{`GitHub Projects`}</Popover.Title>
<Popover.Content>
<strong>{userName}'s GitHub Projects:</strong>
{projectData.length > 0 && (
<GitHubListDisplay {...projectData} />
)}
</Popover.Content>
</Popover>
}
>
<Button variant="link">{userName}</Button>
</OverlayTrigger>
</>
);
}
Related
Im Having a Table which has multiple records and Filter component with the Search Bar. What im trying to do is Based on the value selected by the user from all the filters i have pass those arrays to parent and form an object,
Im having 3 components here,
1)Parent : Data
export default function Data(props) {
const [domain, setDomain] = useState([]);
const [fileType, setFileType] = useState([]);
const [entity, setEntity] = useState(["Patents"]);
const [year, setYear] = useState({});
//This is the search bar state
const [keywords, setKeywords] = useState([]);
//based on the filter values im calling the API to get the records for table based on the value selected by the user from my filer
useEffect(() => {
const fetchResults = async (projectid) => {
const url = props.apiURL.rpaapiurl + "/search";
console.log("fetchData called-->" + url);
const resultsObj = {
projectId: projectid,
filter: {
domain: domain,
fileType: fileType,
entity: entity,
},
};
const response = await fetch(url, {
method: "POST",
body: JSON.stringify(resultsObj),
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
console.log("All data-->", data);
setResults(data);
};
fetchResults(5);
}, [domain, fileType, entity]);
const handleFileType = (fileTypeArray) => {
setFileType(fileTypeArray);
};
return (
<Item1>
<Dropdown onChangeFileType={(FileTypeFilteredArray) => handleFileType(FileTypeFilteredArray)} ></Dropdown>
</Item1>
<Item2>
<Table
Data={dataresults}
Attributes={resultTable}
entitytypeHandler={props.entitytypeHandler}
></Table>
</Item2>
)
From the data parent component im passing the hadler which will return updated array from the child and im setting it to state.
2)Child : Dropdown
export default function Dropdown(props) {
return (
<FilterItem>
<Input
type="search"
placeholder="Search in title, description, keywords"
></Input>
<Filter1></Filter1>
<Filetr2></Filetr2>
<ContentFormat
onChangeFileType={props.onChangeFileType}
></ContentFormat>
<Filter4></Filter4>
<Filter5></Filter5>
<TextWrap>
<P text="End year" fontSize="14px" color="#454545"></P>
<KeywordImg src={droparrow} />
</TextWrap>
</FilterItem>
)}
Nothing special here since we can not skip a component passing the same thing to nested child,
Nested Child : ContentFormat
export default function ContentFormat(props) {
const [isDisplay, setIsDisplay] = useState("false");
const array = ["HTML", "PDF"];
const toggle = () => {
setIsDisplay(!isDisplay);
};
let fileTypeArray = [];
const handleSelection = (event) => {
const value = event.target.value;
console.log("value-->", +value);
if (event.target.checked == true) {
fileTypeArray.push(value);
console.log("if fileTypeArray-->", fileTypeArray);
} else if (fileTypeArray.length > 0) {
fileTypeArray = fileTypeArray.filter((element) => {
console.log("element-->", +element);
if (event.target.value !== element) return element;
});
console.log("else fileTypeArray-->", fileTypeArray);
}
console.log("function fileTypeArray-->", fileTypeArray);
};
const applyClickHandler = () => {
console.log("Applied fileTypeArray-->", fileTypeArray);
props.onChangeFileType(fileTypeArray);
};
return (
<div>
<DropContent>
<DropButton onClick={toggle}>
{" "}
<P text="By Content Format" fontSize="14px" color="#454545"></P>
<KeywordImg src={droparrow} />
</DropButton>
<ContextWrapper style={{ display: isDisplay ? "none" : "block" }}>
<P
text="Filter by Extension types"
fontSize="18px"
color="#ACACAC"
textAlign="center"
padding="22px 32px 14px"
></P>
<DropScroll className="sl-style-3">
{array.map((item, index) => {
return (
<ContextItem key={index}>
<DropList
onHandleSelection={handleSelection}
text={item}
value={item}
></DropList>
</ContextItem>
);
})}
</DropScroll>
<ApplyButton onClick={applyClickHandler}>
<P text="Apply" fontSize="16px" color="#fff" textAlign="center"></P>
</ApplyButton>
</ContextWrapper>
</DropContent>
</div>
);
}
4)DropList
export default function DropList(props) {
const changeHandler = (e) => {
console.log(e);
props.onHandleSelection(e);
};
return (
<div>
<div className="">
<TickBox
type="checkbox"
id={props.id}
name={props.name}
value={props.value}
onChange={(e) => {
changeHandler(e);
}}
/>
{props.text}
</div>
</div>
);
}
I'm getting the updated array on click of apply button in the parent but if user un-selects any check box the it deleting the complete array
In data i have to form the object base on the state array passed by all the filters, i tried for the one filter as above but its not working can any one suggest better way to do it,
Because here handling one filter is default and i have to do it for total 5 filters
So any suggestion or one common component for all the filters
Im not sure whether i should be asking these kinda questions or not since I'm very at posting the right questios but pardon me if its wrong question or the way of asking is wrong,
Any help would be appricited.
I'm fetching an object (with a text value and a few arrays) from an API and transferring those to local variables for use. All is working except for when that object I'm fetching doesn't have one of those arrays and I try to use it the whole site crashes. I'm lost on how to do the error handling here.
import React, { useEffect, useState } from 'react'
import classes from './Streaming.module.css'
const Streaming = (props) => {
const [streamingOn, setStreamingOn] = useState(false)
const [streamingData, setStreamingData] = useState(null)
async function receiveStreaming() {
await fetch(`https://api.themoviedb.org/3/movie/${props.movie}/watch/providers?
api_key=35135143f12a5c114d5d09d17dfcea12`)
.then(res => res.json())
.then(result => {
setStreamingData(result.results.US)
setStreamingOn(true)
}, (error) => {
console.error("Error: ", error)
}
)
// console.log(data)
}
const displayStreaming = streamingData => {
let sortedData = { ...streamingData }
let streamData = sortedData.flatrate
let rentData = sortedData.rent
let linkText = streamingData.link
let id = Math.random()
let streamListItems = streamData.map((movie) =>
<li key={id}>
<a href={linkText}><img className={classes.logoimg} src=. {'https://image.tmdb.org/t/p/w500/' + movie.logo_path}></img></a>
</li>)
let rentListItems = rentData.map((movie) =>
<li key={id}>
<a href={linkText}><img className={classes.logoimg} src={'https://image.tmdb.org/t/p/w500/' + movie.logo_path}></img></a>
</li>)
return (
<React.Fragment>
<p>Stream on</p>
<ul className={classes.logolist}>{streamListItems}</ul>
<p>Rent on</p>
<ul className={classes.logolist}>{rentListItems}</ul>
</React.Fragment>
)
// console.log(sortedData)
}
return (
<React.Fragment>
<button onClick={receiveStreaming}></button>
{<div className={classes.streaminglogos}>
{(streamingOn) && <div>{displayStreaming(streamingData)}</div> }
</div>}
</React.Fragment>
)
}
export default Streaming
Use optional chaining to check the expected array has been received or not.
Assuming that you need to show an error UI when the expected array was not received, then you can set a flag(isErrored) to true and render that conditionally.
Handling Response JSON
if (!result?.results?.US) {
setIsErrored(true);
} else {
setStreamingData(result.results.US)
setStreamingOn(true);
}
Rendering Error UI conditionally
{isErrored && (<ErrorUI />)}
There are a few things you can do here. The first is that you could check if the array exists when you first get it and then append it on to it if it doesn't.
Maybe something like:
if(!result.results.US){
result.results.US = []
}
Or you could check if the array exists when you are displaying the data by conditionally rendering the component (or piece of component). If the data does not have the array (using the above method) don't display it.
Hope this helps!
I'm pulling stuff from my database using Firestore. When I log inside the function that pulls the data, the array has the data. When I log in my main component, it also has. But for some reason, .map doesn't work, and when I try array.length it returns 0. I was using a map, but then I changed it to use a function to try to get the error.
export default function Search() {
const [searchedData, setSearchedData] = useState([]);
const [loading, setLoading] = useState(true);
const [noBook, setNoBook] = useState(false);
const [showBooks, setShowBooks] = useState(false);
const link = useLocation();
useEffect(() => {
const srch = link.pathname.substring(8);
loadSearchBooks(srch);
}, [link]);
async function loadSearchBooks(srch) {
try {
const bookArray = await getSearchedBooks(srch);
bookArray ? setShowBooks(true) : setNoBook(true);
setSearchedData(bookArray);
} catch (e) {
setSearchedData(null);
} finally {
setLoading(false);
}
}
function renderBooks() {
console.log(searchedData);
const l = searchedData.length;
return l;
}
return (
<div>
<Navbar />
<div className={searchBookWrapper}>
{loading && 'Carregando'}
{showBooks && renderBooks()}
{noBook && <BookCardItem />}
</div>
</div>
);
}
When doing this, console.log(searchedData) returns the array, but const l = searchedData.length shows just a 0. When I search again, the number changes to 12 for a moment right when it's about to change. This is the previous code:
return (
<div>
<Navbar />
<div className={searchBookWrapper}>
{loading && 'Carregando'}
{showBooks &&
searchedData.map(({ afn, aln, notes, quant, title }, index) => {
return (
<BookCardItem
key={title}
firstName={afn}
lastName={aln}
notes={notes}
quant={quant}
title={title}
bookNumber={index}
/>
);
})}
{noBook && <BookCardItem />}
</div>
</div>
);
}
The same thing happened. The bookInfo appeared just for a moment when I searched again.
From the first code in this question, this is the console:
Console - one empty array, then two filled ones
if useLocation is an API call or other async function, react prefers those to be in a useEffect. If they are not, it can give inconsistent results. Try putting useLocation inside a useEffect. If you only plan on useLocation firing once (and thus loadSearchedBooks only firing once) you can even put them in the same useEffect, just don't make them rerender based on the thing they update.
useEffect(() => {
useLocation().then(link => {
const srch = link.pathname.substring(8);
loadSearchBooks(srch);
}
}, []);
Hopefully, this will fix your problem.
I'm learning to use firebase and react. I have shared my firestore collection image. and my code for fetching the array from my document is given below.
This code is fetching the data from my firestore database and then storing the result in my watchlistMovies react state. when i try to log the react state or even data.data() it gives the desired result but when i try to map over the array or do something similar like logging watchlistMovies.myList[0].media_type it hits me with an error. i tried my best trying different things making it work but it breaks a thing or two in process.
I hope someone here will help me. Thank you in advance! : )
updated the code
const Watchlist = () => {
const [watchlistMovies, setwatchlistMovies] = useState([]);
const {currentUser} = useAuth()
const usersCollectionRef = collection(db,"users")
const docRef = doc(db,"users",currentUser.uid)
useEffect(() => {
const getWatchListMovies = async () => {
const data = await getDoc(docRef)
if (data.exists()) {
console.log(data.data());
setwatchlistMovies([...watchlistMovies ,data.data().myList])
} else {
console.log("empty");
}
}
getWatchListMovies();
}, [])
console.log(watchlistMovies);
// console.log(watchlistMovies.myList[0]);
return (
<div className="content-page-area">
<h1 className="trending-text"> My Watchlist </h1>
<Container className="watchlist-container">
<hr/>
{watchlistMovies.map(
(item) => (
<ListContent
item_poster={item.poster_url}
item_title={item.media_title}
item_year={item.release_year}
item_rating={item.media_rating}
item_type={item.media_type}
item_id={item.media_id}
/>
)
)}
</Container>
<br/>
<br/>
<br/>
</div>
)
}
export default Watchlist
So, I am running into this problem where I am making filters while using the one graphql query. There's a child, parent & grandparent(these are different components). Now my query uses variables inside it, initially on load I set the variables in useState and it's working fine. Now when I click on a checkbox(which is insinde Child component) it passed its data(which is variable for new query) to the Grandparent and I am getting that, so I pass that data into the query variable. But it's not re-redering the query again with new variable. So my filters are not working.
Grand Parent
// handling all the product grid actions and data
function ProductGrid() {
const [queryVariables, setQueryVariables] = useState({first: 20});
console.log(queryVariables);
// get the variable object
const { loading, error, data, fetchMore} = useQuery(QUERY, {
variables: queryVariables
});
if (loading) return <p>Loading...</p>;
if (error) return (
<p>
{console.log(error)}
</p>
);
let productEdges = data.products.edges;
return(
<div className="outContainer">
{/* <PriceFilter/> */}
<TypeFilter getFilters={queryVariables => setQueryVariables(queryVariables)} />
{/* test button */}
<div className="product-grid">
{productEdges.map((element, index) => {
// formatting the price
let tempPrice = Math.floor(element.node.priceRange.minVariantPrice.amount);
let productPrice = new Intl.NumberFormat().format(tempPrice);
return(
<div className="container" key={index}>
<div className="image-container">
<img src={element.node.images.edges[0].node.transformedSrc} alt={element.node.title} />
</div>
<div className="product-title">{element.node.title}</div>
<div>{element.node.priceRange.minVariantPrice.currencyCode}. {productPrice}</div>
</div>
)
})}
</div>
{/* load more products button */}
<button
className="load-more"
onClick={()=>{
const endCursor = data.products.edges[data.products.edges.length - 1].cursor;
fetchMore({
variables: {
after: endCursor,
queryVariables
}
})
}}>
Load More
</button>
</div>
)
}
// graphql query for products fetching
const QUERY = gql`
query productFetch($first:Int, $after:String, $query:String){
products(first:$first, after: $after, query:$query){
edges{
node{
priceRange{
minVariantPrice{
amount
currencyCode
}
}
title
images(first:1){
edges{
node{
transformedSrc(maxWidth: 300)
}
}
}
}
cursor
}
pageInfo{
hasNextPage
}
}
}
`
Parent
// ************** Parent ***************
function TypeFilter(props) {
// assume other code is here for a modal pop and
accordion inside here
// passing the prop to checkbox component here and
getting back new state which we use as a callback for
it's parent
<TypeCheckBox getCheckState={queryVariables =>
props.getFilters(queryVariables)} />
}
Child
// ************** Child *****************
let result = "";
let variables =
{
first: 28,
query: ""
};
function TypeCheckBox(props){
// below function returns variables for apollo query
const handleCheckChange = (event) => {
setState({ ...state, [event.target.name]: event.target.checked });
if(event.target.checked){
// pass this value into the productGrid component
if(counter > 1){
result += "&";
}
result = `${result}product_type:${event.target.value}`;
counter++;
// setting filter type to result
console.log(result);
variables.query = result;
console.log(variables);
return props.getCheckState(variables);
}else{
result = result.replace(`product_type:${event.target.value}`, "");
result = removeLast(result, "&");
counter--;
// setting filter type to result
console.log(`in else ${result}`);
variables.query = result;
console.log(variables);
return props.getCheckState(variables);
}
};
}
return (
<FormGroup column>
<FormControlLabel
control={<Checkbox checked={state.checkedBridal} value="Bridal" onChange={handleCheckChange} name="checkedBridal" />}
label="Bridals"
/>
)
}
I tried using useEffect on useQuery in GrandParent, but then the returned constants don't have access outside, like
useEffect(() => {
const { loading, error, data, fetchMore} = useQuery(QUERY, {
variables: queryVariables
});
}, [queryVariables])
Thanks you soo much for your answers ^^
So, I figured out a solution since no one answered it so I am gonna answer it.
first, you need to add refetch and fetch-policy in useQuery hook as
const { loading, error, data, fetchMore, refetch, networkStatus } = useQuery(
QUERY,
{
variables: queryVariables,
// notifyOnNetworkStatusChange: true,
fetchPolicy: "network-only"
}
);
Then you need to make a separate function with the same name as your props that you're passing to your children and use the spread operator with queryVariables to expand and refetch the query with new variables as
const getFilters = queryVariables => {
setQueryVariables({ ...queryVariables });
refetch();
};
Hope this is helpful to someone ^^