Proper way to fetch from another component - ReactJS - javascript

I have a component that fetches the data properly but I want to encapsulate it in a helper. I've tried many things but I'm stuck.
This is the component that works:
export const Carousel = () => {
const [ lotteries, setLotteries ] = useState({});
const [ isLoading, setisLoading ] = useState(true);
useEffect(() => {
async function fetchAPI() {
const url = 'https://protected-sea-30988.herokuapp.com/https://www.lottoland.com/api/drawings;'
let response = await fetch(url)
response = await response.json()
setLotteries(response)
setisLoading(false)
}
fetchAPI()
}, [])
return (
<>
{
isLoading ? (
<span>loading</span>
) : (
<Slider >
{
Object.keys(lotteries).map((lottery, idx) => {
return (
<Slide
key={ idx }
title={ lottery }
prize={ lotteries[lottery].next.jackpot }
day={ lotteries[lottery].next.date.day }
/>
)
})
}
</Slider>
)}
</>
);}
And this is the last thing I've tried so far. This is the component without the fetch
export const Carousel = () => {
const [ lotteries, setLotteries ] = useState({});
const [ isLoading, setIsLoading ] = useState(true);
useEffect(() => {
getLotteries()
setLotteries(response)
setIsLoading(false)
}, [])
And this is where I tried to encapsulate the fetching.
export const getLotteries = async() => {
const url = 'https://protected-sea-30988.herokuapp.com/https://www.lottoland.com/api/drawings;'
let response = await fetch(url)
response = await response.json()
return response;
}
I'm a bit new to React, so any help would be much appreciated. Many thanks.

To get the fetched data from getLotteries helper you have to return a promise
export const getLotteries = async() => {
const url = 'https://protected-sea-
30988.herokuapp.com/https://www.lottoland.com/api/drawings;'
let response = await fetch(url)
return response.json()
}
and call it as async/await
useEffect(async() => {
let response= await getLotteries()
setLotteries(response)
setIsLoading(false)
}, [])

If you want to separate the logic for requesting a URL into another helper function, you can create a custom hook.
// customHook.js
import { useEffect, useState } from 'react';
export function useLotteries() {
const [lotteries, setLotteries] = useState(null);
useEffect(() => {
fetch('https://protected-sea-30988.herokuapp.com/https://www.lottoland.com/api/drawings;')
.then(response => response.json())
.then(json => setLotteries(json));
}, []);
return lotteries;
}
// Carousel.js
import { useLotteries } from "./customHook.js";
export const Carousel = () => {
const lotteries = useLotteries();
if (lotteries) {
return; /* Your JSX here! (`lotteries` is now contains all the request responses) */
} else {
return <Loader />; // Or just null if you don't want to show a loading indicator when your data hasn't been received yet.
}
};

Related

How to pass data from an axios API inside a state using React?

I have an api (an arr of objects) which I need to pass into a state, so that I can then pass that data inside a component to show it on the website.
1st approach:
// pulls the api data
const newData = axios.get(url).then((resp) => {
const apiData = resp.data;
apiData.map((video) => {
return video;
});
});
// sets the state for the video
const [selectedVideo, setSelectedVideo] = useState(newData[0]);
const [videos] = useState(videoDetailsData);
...
return (
<>
<FeaturedVideoDescription selectedVideo={selectedVideo} />
</>
)
2nd approach:
const useAxiosUrl = () => {
const [selectedVideo, setSelectedVideo] = useState(null);
useEffect(() => {
axios
.get(url)
.then((resp) => setSelectedVideo(resp.data))
});
return selectedVideo;
}
...
return (
<>
<FeaturedVideoDescription selectedVideo={selectedVideo} />
</>
)
both of these approaches don't seem to work. What am I missing here?
The correct way is to call your axios method inside the useEffect function.
const fetchData = axios.get(url).then((resp) => setSelectedVideo(resp.data)));
useEffect(() => {
fetchData();
}, [])
or if you need async/await
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(url);
setSelectedVideo(resp.data);
}
fetchData();
}, [])

What is happening here with React?

I am making a request to my API from the client. I set the information in a React state. The weird thing is the following:
When I print the result of the fetch by console I get this:
But when I display to see in detail, specifically the "explore" array, I get that the result is empty:
The explore array is passed to two components by props to show a list, the super weird thing is that one of the components shows the information but the other does not
Fetch
const baseUrl = process.env.BASE_API_URL;
import { marketPlace } from "./mock";
import { requestOptions } from "utils/auxiliaryFunctions";
const getInitialMarketPlaceData = async (username: string) => {
try {
return await fetch(
`${baseUrl}marketplace/home/${username}`,
requestOptions()
)
.then((data) => data.json())
.then((res) => {
console.log(res.data)
if (res.success) {
return res.data;
}
});
} catch (err) {
throw err;
}
};
Components
export default function Marketplace() {
const [marketPlace, setMarketPlace] = useState<any>();
const [coachData, setCoachData] = useState<false | any>();
const [coach, setCoach] = useState<string>();
const { user } = useContext(AuthContext);
useEffect(() => {
if (!marketPlace) {
getInitialMarketPlaceData(user.username).then((data) => {
console.log("data", data);
setMarketPlace(data);
});
}
}, []);
useEffect(() => {
console.log("marketplace", marketPlace);
}, [marketPlace]);
useEffect(() => {
console.log("coachData", coachData);
}, [coachData]);
const changeCoachData = async (coach: string) => {
setCoach(coach);
let res = await getCoachProfile(coach);
setCoachData(res);
};
if (!marketPlace) {
return <AppLoader />;
}
return (
<main className={styles.marketplace}>
{marketPlace && (
// Highlited viene vacío de la API
<Highlited
highlitedCoaches={/*marketPlace.highlighted*/ marketPlace.explore}
></Highlited>
)}
{marketPlace.favorites.length !== 0 && (
<Favorites
changeCoachData={changeCoachData}
favCoaches={marketPlace.favorites}
></Favorites>
)}
{
<Explore
changeCoachData={changeCoachData}
exploreCoaches={marketPlace.explore}
></Explore>
}
{/*<Opinions
changeCoachData={changeCoachData}
opinions={marketPlace.opinions}
></Opinions>*/}
{coachData && (
<CoachProfileRookieView
coachData={coachData}
coachUsername={coach}
isCoach={false}
isPreview={false}
onClose={() => setCoachData(false)}
/>
)}
</main>
);
}

Reactjs Empty State when Page Load

API: https://developers.themoviedb.org/
const [searchQuery, setSearchQuery] = useState();
const [searchResults, setsearchResults] = useState([]);
const getSearchResults = () => {
baseService.get(`/search/multi?api_key=${API_KEY}&language=en-US&query=${searchQuery}`)
.then(data=>setsearchResults(data.results))
}
useEffect(() => {
getSearchResults()
console.log(searchResults)
}, [searchQuery])
return (
<Container>
<TextField color="primary" label="Search for anything" size="small" onChange={(e) => setSearchQuery(e.target.value)}/>
{searchResults && searchResults.map((search,key) => (
<span key={key}>{search?.title}</span>
))}
</Container>
baseservice.js is like that
import { API_URL } from "./config"
export const baseService = {
get: async (url) => {
let response = [];
await fetch(API_URL+url, {
})
.then((res) => res.json())
.then((data) => {
response = data
})
return response;
}
}
1.Picture is when page load.
2.Picure is when entry search term.
In baseService.get you’re returning response before the promise has resolved, it can be simplified to this:
export const baseService = {
get: (url) => {
return fetch(API_URL+url)
.then((res) => res.json())
}
}
This happens because you're logging searchResults inside a useEffect that has a searchQuery dependency, so it logs the first time state are set and then it doesn't log when it changes from fetch response. If you wanna log it correctly you have to add searchResults as a dependecy.
useEffect(() => {
getSearchResults()
console.log(searchResults)
}, [searchQuery, searchResults])

How to organize async fetching code in Reactjs

In many components, I need to fetch some data and I'm ending up with a lot of similar code. It looks like this:
const [data, setData] = useState();
const [fetchingState, setFetchingState] = useState(FetchingState.Idle);
useEffect(
() => {
loadDataFromServer(props.dataId);
},
[props.dataId]
);
async function loadDataFromServer(id) {
let url = new URL(`${process.env.REACT_APP_API}/data/${id}`);
let timeout = setTimeout(() => setFetchingState(FetchingState.Loading), 1000)
try {
const result = await axios.get(url);
setData(result.data);
setFetchingState(FetchingState.Idle);
}
catch (error) {
setData();
setFetchingState(FetchingState.Error);
}
clearTimeout(timeout);
}
How can I put it into a library and reuse it?
Thank you guys for the suggestion, I came up with the following hook. Would be happy to some critics.
function useFetch(id, setData) {
const [fetchingState, setFetchingState] = useState(FetchingState.Idle);
useEffect(() => { loadDataFromServer(id); }, [id]);
async function loadDataFromServer(id) {
let url = new URL(`${process.env.REACT_APP_API}/data/${id}`);
let timeout = setTimeout(() => setFetchingState(FetchingState.Loading), 1000)
try {
const result = await axios.get(url);
setData(result.data);
setFetchingState(FetchingState.Idle);
}
catch (error) {
setData();
setFetchingState(FetchingState.Error);
}
clearTimeout(timeout);
}
return fetchingState;
}
And this is how I use it:
function Thread(props) {
const [question, setQuestion] = useState();
const fetchingState = useFetch(props.questionId, setQuestion);
if (fetchingState === FetchingState.Error) return <p>Error while getting the post.</p>;
if (fetchingState === FetchingState.Loading) return <Spinner />;
return <div>{JSON.stringify(question)}</div>;
}
You can wrap your APIs calls in /services folder and use it anywhere
/services
- Auth.js
- Products.js
- etc...
Example
Auth.js
import Axios from 'axios';
export const LoginFn = (formData) => Axios.post("/auth/login", formData);
export const SignupFn = (formData) => Axios.post("/auth/signup", formData);
export const GetProfileFn = () => Axios.get("/auth/profile")
in your component
import React, { useState } from 'react'
import { LoginFn } from '#Services/Auth'
export LoginPage = () => {
const [isLoading, setIsLoading] = useState(false);
const LoginHandler = (data) => {
setIsLoading(true)
LoginFn(data).then(({ data }) => {
// do whatever you need
setIsLoading(false)
})
}
return (
<form onSubmit={LoginHandler}>
.......
)
}

nested async - best practices

I am making dummy app to test server side API.
First request returns nested JSON object with Product names and number of variants that it has. From there I extract Product name so I can send second request to fetch list of variants with product images, sizes etc.
Sometimes it will load and display variants from only one product but most of the times it will work correctly and load all variants from both dummy products.
Is there a better way of doing this to ensure it works consistently good. Also I would like to know if there is a better overall approach to write something like this.
Here is the code:
import React, { useEffect, useState } from "react";
import axios from "axios";
import ShirtList from "../components/ShirtList";
const recipeId = "15f09b5f-7a5c-458e-9c41-f09d6485940e";
const HomePage = props => {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
axios
.get(
`https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
)
.then(response => {
let shirtList = [];
const itemsLength = response.data.Products.length;
response.data.Products.forEach((element, index) => {
axios
.get(
`https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${element.ProductName}`
)
.then(response => {
shirtList.push(response.data.Variants);
if (index === itemsLength - 1) {
setLoaded(shirtList);
}
});
});
});
}, []);
const ListItems = props => {
if (props.loaded) {
return loaded.map(item => <ShirtList items={item} />);
} else {
return null;
}
};
return (
<div>
<ListItems loaded={loaded} />
</div>
);
};
export default HomePage;
You are setting the loaded shirts after each iteration so you will only get the last resolved promise data, instead fetch all the data and then update the state.
Also, separate your state, one for the loading state and one for the data.
Option 1 using async/await
const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'
const fetchProducts = async () => {
const { data } = await axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
return data.Products
}
const fetchShirts = async productName => {
const { data } = await axios.get(
`${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
)
return data.Variants
}
const HomePage = props => {
const [isLoading, setIsLoading] = useState(false)
const [shirtList, setShirtList] = useState([])
useEffect(() => {
setIsLoading(true)
const fetchProductShirts = async () => {
const products = await fetchProducts()
const shirts = await Promise.all(
products.map(({ productName }) => fetchShirts(productName)),
)
setShirtList(shirts)
setIsLoading(false)
}
fetchProductShirts().catch(console.log)
}, [])
}
Option 2 using raw promises
const recipeId = '15f09b5f-7a5c-458e-9c41-f09d6485940e'
const BASE_URL = 'https://api.print.io/api/v/5/source/api'
const fetchProducts = () =>
axios.get(`${BASE_URL}/prpproducts/?recipeid=${recipeId}&page=1`)
.then(({ data }) => data.Products)
const fetchShirts = productName =>
axios
.get(
`${BASE_URL}/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`,
)
.then(({ data }) => data.Variants)
const HomePage = props => {
const [isLoading, setIsLoading] = useState(false)
const [shirtList, setShirtList] = useState([])
useEffect(() => {
setIsLoading(true)
fetchProducts
.then(products) =>
Promise.all(products.map(({ productName }) => fetchShirts(productName))),
)
.then(setShirtList)
.catch(console.log)
.finally(() => setIsLoading(false)
}, [])
}
Now you have isLoading state for the loading state and shirtList for the data, you can render based on that like this
return (
<div>
{isLoading ? (
<span>loading...</span>
) : (
// always set a unique key when rendering a list.
// also rethink the prop names
shirtList.map(shirt => <ShirtList key={shirt.id} items={shirt} />)
)}
</div>
)
Refferences
Promise.all
Promise.prototype.finally
React key prop
The following should pass a flat array of all variants (for all products ) into setLoaded. I think this is what you want.
Once all the products have been retrieved, we map them to an array of promises for fetching the variants.
We use Promise.allSettled to wait for all the variants to be retrieved, and then we flatten the result into a single array.
useEffect(()=>(async()=>{
const ps = await getProducts(recipeId)
const variants = takeSuccessful(
await Promise.allSettled(
ps.map(({ProductName})=>getVariants({ recipeId, ProductName }))))
setLoaded(variants.flat())
})())
...and you will need utility functions something like these:
const takeSuccessful = (settledResponses)=>settledResponses.map(({status, value})=>status === 'fulfilled' && value)
const productURL = (recipeId)=>`https://api.print.io/api/v/5/source/api/prpproducts/?recipeid=${recipeId}&page=1`
const variantsURL = ({recipeId, productName})=>`https://api.print.io/api/v/5/source/api/prpvariants/?recipeid=${recipeId}&page=1&productName=${productName}`
const getProducts = async(recipeId)=>
(await axios.get(productURL(recipeId)))?.data?.Products
const getVariants = async({recipeId, productName})=>
(await axios.get(variantsURL({recipeId,productName})))?.data?.Variants

Categories