How manage data when its already fetched by Axios - javascript

I am using a database with MySQL and getting it using Axios and a useEffect. Then I pass my database data to a component using a prop. Like this:
const Component = () => {
//DB
const urlProxy = "/api/cities";
//Hooks
const [data, setData] = useState([]);
//DB Fetch
const fetchData = async () => {
await axios
.get(urlProxy)
.then((res) => {
setData(res.data);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
return () => {
fetchData();
};
}, []);
return (
<>
<h1>Cities</h1>
<Cities api={data} />
</>
);
};
Inside of Cities Component I want to make an algorithm to manipulate that data, but I get one empty array (from const [data, setData] = useState([]). After a moment I get the fetched data from database.
const Cities = (api) => {
console.log(data) // First Print: [{api:Array(0)}] then [{api:Array(2)}]
return(
<>
...
</>
)
}
So if it prints at first an empty array I would get an error
I was thinking of using a useTimeout() but i don't know if there is a better solution, in order to use data after it's fetched.

All you would need to do is manipluate the data before you set it into your state
and the best way to wait until that is done is to have a loading state that waits for your data to be pulled and then have your useEffect manipulate it.
Something like this should work for you
const urlProxy = "/api/cities";
const Component = () => {
const [data, setData] = useState();
const [loading, setLoading] = useState(true);
//DB Fetch
const fetchData = async () => {
await axios
.get(urlProxy)
.then((res) => {
// Manipulate your data here before you set it into state
const manipluatedData = manipulateData(res.data)
setData(manipluatedData);
})
.catch((err) => {
console.log(err);
})
.finally(() =>
setLoading(false);
})
};
useEffect(() => {
return () => {
fetchData();
};
}, []);
if(loading){
return 'loading....'
}
return (
<>
<h1>Cities</h1>
<Cities api={data} />
</>
);
};

Related

Search function now working in React photo gallary

Working on a small application that takes a pexels api and displays photos dynamically. When I send the search request for my api to fectch based on the new params, it does actually update the page with new photos but not the ones based on the params. I though I got the search function correct, maybe it's cause I'm not using it in a useEffect? But if I did use it in a useEffect, I wouldn't be able to set it on the onClick handle. I tried to console.log the query I was getting from the onChange but it doesn't seem like it's getting the result. What am I doing wrong?
import { useState, useEffect } from 'react'
import pexelsApi from './components/pexelsApi'
import './App.css'
const App = () => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const [nextPage, setNextPage] = useState(1);
const [perPage, setPerPage] = useState(25);
const [query, setQuery] = useState('');
const [error, setError] = useState('');
useEffect(() => {
const getImages = async () => {
setLoading(true);
await pexelsApi.get(`/v1/curated?page=${nextPage}&per_page=${perPage}`)
.then(res => {
setImages([...images, ...res.data.photos]);
setLoading(false);
}).catch(er => {
if (er.response) {
const error = er.response.status === 404 ? 'Page not found' : 'Something wrong has happened';
setError(error);
setLoading(false);
console.log(error);
}
});
}
getImages();
}, [nextPage, perPage]);
const handleLoadMoreClick = () => setNextPage(nextPage + 1)
const search = async (query) => {
setLoading(true);
await pexelsApi.get(`/v1/search?query=${query}&per_page=${perPage}`)
.then(res => {
setImages([...res.data.photos]);
console.log(res.data)
setLoading(false);
console.log(query)
})
}
if (!images) {
return <div>Loading</div>
}
return (
<>
<div>
<input type='text' onChange={(event) => setQuery(event.target.value)} />
<button onClick={search}>Search</button>
</div>
<div className='image-grid'>
{images.map((image) => <img key={image.id} src={image.src.original} alt={image.alt} />)}
</div>
<div className='load'>
{nextPage && <button onClick={handleLoadMoreClick}>Load More Photos</button>}
</div>
</>
)
};
export default App
import axios from 'axios';
export default axios.create({
baseURL: `https://api.pexels.com`,
headers: {
Authorization: process.env.REACT_APP_API_KEY
}
});
Your main issue is that you've set query as an argument to your search function but never pass anything. You can just remove the arg to have it use the query state instead but you'll then need to handle pagination...
// Helper functions
const getCuratedImages = () =>
pexelsApi.get("/v1/curated", {
params: {
page: nextPage,
per_page: perPage
}
}).then(r => r.data.photos)
const getSearchImages = (page = nextPage) =>
pexelsApi.get("/v1/search", {
params: {
query,
page,
per_page: perPage
}
}).then(r => r.data.photos)
// initial render effect
useEffect(() => {
setLoading(true)
getCuratedImages().then(photos => {
setImages(photos)
setLoading(false)
})
}, [])
// search onClick handler
const search = async () => {
setNextPage(1)
setLoading(true)
setImages(await getSearchImages(1)) // directly load page 1
setLoading(false)
}
// handle pagination parameter changes
useEffect(() => {
// only action for subsequent pages
if (nextPage > 1) {
setLoading(true)
const promise = query
? getSearchImages()
: getCuratedImages()
promise.then(photos => {
setImages([...images, ...photos])
setLoading(false)
})
}
}, [ nextPage ])
The reason I'm passing in page = 1 in the search function is because the setNextPage(1) won't have completed for that first page load.

React-Native infinite loop

I am trying to get data from my firebase-firestore I an showing a loading state to wait for the data to load however when it does load it keeps returning the firestore data infinite times. Please may someone help me.
This is my code Paper is just a custom component
import Paper from '../Components/Paper'
import firebase from 'firebase'
import { useState } from 'react'
const Home = (props) => {
const renderMealItem = (itemData) =>{
return (
<Paper
title={itemData.item.name}
serves={itemData.item.servings}
time={itemData.item.time}
image={itemData.item.imageUri}
/>
)
}
const [loading, setLoading] = useState(false)
const [all, setAll] = useState([])
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
},[])
const checkReturn = () => {
if(all !== undefined){
setLoading(false)
}
}
const getUser = async() => {
try {
await firebase.firestore()
.collection('Home')
.get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
});
}catch(err){
console.log(err)
}
}
return(
<View style={styles.flatContainer}>
<FlatList
data={all}
keyExtractor={(item, index) => index.toString()}
renderItem={renderMealItem}/>
</View>
)
}
useEffect without second parameter will get executes on each update.
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
})
so this will set the loading and tries to get the user. and when the data comess from server, it will get runned again.
So you should change it to : useEffect(() => {...}, []) to only get executed on mount phase(start).
Update: you should check for return on every update, not only at start. so change the code to:
useEffect(() => {
setLoading(true)
getUser()
}, [])
useEffect(() => {
checkReturn()
})
Ps: there is another issue with your code as well:
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
maybe it should be like :
setAll(querySnapshot.docs.map(doc => JSON.stringify(doc.data())));
Try passing an empty array as an argument to useEffect like so:
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
}, [])

Edit data when using a custom data fetching React Hook?

I've been trying to make a chart with data fetched from an API that returns data as follows:
{
"totalAmount": 230,
"reportDate": "2020-03-05"
},
{
"totalAmount": 310,
"reportDate": "2020-03-06"
}
...
The date string is too long when displayed as a chart, so I want to shorten it by removing the year part.
2020-03-06 becomes 03/06
Following a great tutorial by Robin Wieruch, I now have a custom Hook to fetch data:
const useDataApi = (initialUrl, initialData) => {
const [data, setData] = useState(initialData);
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url]);
return [{ data, isLoading, isError }];
};
Along with my charting component written in React Hooks:
const MyChart = () => {
const [{ data, isLoading, isError }] = useDataApi(
"https://some/api/domain",
[]
);
useEffect(() => {
// I'm using useEffect to replace every date strings before rendering
if (data) {
data.forEach(
d =>
(d.reportDate = d.reportDate
.replace(/-/g, "/")
.replace(/^\d{4}\//g, ""))
);
}
}, [data]);
return (
<>
<h1>My Chart</h1>
{isError && <div>Something went wrong</div>}
{isLoading ? (
. . .
) : (
<>
. . .
<div className="line-chart">
<MyLineChart data={data} x="reportDate" y="totalAmount" />
</div>
</>
)}
</>
);
};
The above works. But I have a feeling that this might not be the best practice because useEffect would be called twice during rendering. And when I try to adopt useReducer in my custom Hook, the code does not work anymore.
So I'm wondering what is the best way to edit data in this circumstance?
You could create a mapping function for your data, which is then used by the hook. This can be done outside of your hook function.
const mapChartDataItem = (dataItem) => ({
...dataItem,
reportDate: dataItem.reportDate.replace(/-/g, "/").replace(/^\d{4}\//g, ""))
});
The reportDate mapping is the same code as you have used in your useEffect.
Then in your hook function:
const data = await response.json();
// this is the new code
const mappedData = data.map(mapChartDataItem);
// change setData to use the new mapped data
setData(mappedData);
Doing it here means that you're only mapping your objects once (when they are fetched) rather than every time the value of data changes.
Update - with injecting the function to the hook
Your hook will now look like this:
const useDataApi = (initialUrl, initialData, transformFn) => {
const [data, setData] = useState(initialData);
const [url, setUrl] = useState(initialUrl);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const response = await fetch(url);
const data = await response.json();
// if transformFn isn't provided, then just set the data as-is
setData((transformFn && transformFn(data)) || data);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, [url, transformFn]);
return [{ data, isLoading, isError }];
};
Then to call it, you can use the following:
const mapChartDataItem = (dataItem) => ({
...dataItem,
reportDate: dataItem.reportDate.replace(/-/g, "/").replace(/^\d{4}\//g, ""))
});
const transformFn = useCallback(data => data.map(mapChartDataItem), []);
const [{ data, isLoading, isError }] = useDataApi(
"https://some/api/domain",
[],
transformFn
);
For simplicity, because the transformFn argument is the last parameter to the function, then you can choose to call your hook without it, and it will just return the data as it was returned from the fetch call.
const [{ data, isLoading, isError }] = useDataApi(
"https://some/api/domain",
[]
);
would work in the same was as it previously did.
Also, if you don't want (transformFn && transformFn(data)) || data in your code, you could give the transformFn a default value in your hook, more along the lines of:
const useDataApi = (
initialUrl,
initialData,
transformFn = data => data) => {
// then the rest of the hook in here
// and call setData with the transformFn
setData(transformFn(data));
}

React hooks reset state and fetch data

I'm building an app with React native. I'm using a FlatList with an
onRefresh handler:
<FlatList
data={data}
renderItem={renderPost}
keyExtractor={(item, index) => index.toString()}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
refreshing={isRefreshing}
onRefresh={handleRefresh}>
</FlatList>
Within that onRefresh handler I reset the data list and fetch new data:
const handleRefresh = () => {
setData([]);
setIsRefreshing(true);
fetchData();
}
The problem is that data is never set to []. I can read here that it's expected behaviour:
useState set method not reflecting change immediately.
But what would be a better way? Because when I use, useEffect I have the same problem:
useEffect(() => {
setData([])
fetchData();
}, [isRefreshing]);
const handleRefresh = () => {
setIsRefreshing(true);
}
isRefreshing is never set to true.
What is the best way of tackling this?
--EDIT
fethData method:
const fetchData = () => {
const url = 'my-api-url.com?page=' + page;
fetch(url, {
method: 'GET',
}).then((response) => response.json())
.then((json) => {
setData(data.concat(json.data));
setIsLoading(false);
setIsRefreshing(false);
});
}
If you get what I'm trying to do here it might work best for you
// how about isolating all the data fetch related hooks
// fetch will be called anytime your request params updates
// qs is from query string library
const useDataFetch = (url, method, params) => {
const [refreshing, setRefreshing] = useState(false)
const [fetching, setFetching] = useState(false)
const [data, setData] = useState([])
useEffect(() => {
async (() => {
const url = `${url}?${qs.stringify(params)}`
// we set fetching to true while data is still to be fetched
await setFetching(true)
const rawResponse = await fetch(url, {method})
// and set it back to false when done
const newData = rawResponse.json().data
if (refreshing) {
setData(newData)
setRefreshing(false)
} else {
setData([...data, ...newData])
}
setFetching(false)
})()
}, [params])
return {refreshing, setRefreshing, fetching, data}
}
// and use it like this
// only params is outside of useDataFetch because of the feature refreshing
export default myApp = () => {
const [params, setParams] = useState({page: 1})
const {refreshing, setRefreshing, fetching, data} = useDataFetch('my-api-url.com', 'GET', params)
const handleRefresh = async () => {
await setRefreshing(true)
setParams({page: 1})
}
return (
<FlatList
data={data}
renderItem={renderPost}
keyExtractor={(item, index) => index.toString()}
onEndReached={handleLoadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={renderFooter}
refreshing={refreshing}
onRefresh={handleRefresh}>
</FlatList>
)
}
// now things are reuseable and less code from now on

How can I return only the set-State from my fetched json data?

im using this Api to get json data.
const FetchEarthquakeData = url => {
const [data, setData] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(jsonData => setData(jsonData.features))
}, [url]);
return data;
};
The problem is when I use this function like this:
const jsonData = FetchEarthquakeData(url)
console.log(jsonData);
I get following console.logs:
null
Array(17)
So my function FetchEarthquakeData returns the null variable and! the desired api. However if I want to map() over the jsonData, the null value gets mapped. How can I refactor my code so I get only the Array?
I'm not quite sure what useState() and setData() do. But in order to fetch the json data from the API, you can make the function as followed, then you can perform operations on the fetched data.
const url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
const FetchEarthquakeData = url => {
return new Promise(resolve => {
fetch(url)
.then(res => res.json())
.then(jsonData => resolve(jsonData.features))
})
}
FetchEarthquakeData(url).then(features => {
console.log(features)
// Do your map() here
})
as per the documentation of react hooks ,Only Call Hooks from React Functions Don’t call Hooks from regular JavaScript functions.
React Function Components -- which are basically just JavaScript Functions being React Components which return JSX (React's Syntax.)
for your requirement you can do as follows in your react component.
idea is to have state hook initialized with empty array then update it once json data available. the fetch logic can be moved inside useeffect .
const SampleComponent=()=>{
const [data,setData] = useState([])
useeffect (){
fetch(url).then((responsedata)=>setData(responseData) ,err=>console.log (err)
}
return (
<>
{
data.map((value,index)=>
<div key=index>{value}<div>
}
</>
)
}
if you find above fetching pattern repetitive in your app thou can use a custom hook for the same.
const useFetch = (url, options) => {
const [response, setResponse] = React.useState(null);
const [error, setError] = React.useState(null);
React.useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
in your React component you can use like
const {data,error} = useFetch(url , options)
You have to do it in an async fashion in order to achieve what you need.
I recommend you doing it separately. In case you need the data to load when the component mounts.
Another thing is that your function is a bit confusing.
Let's assume some things:
const Example = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await fetch(url);
setData(result.features);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};
I am sure that the way you are doing it is storing the data properly on data, but you can not see it on your console.log. It is a bit tricky.
You can read a bit more here => https://medium.com/#wereHamster/beware-react-setstate-is-asynchronous-ce87ef1a9cf3
Adding a bit more of complexity in case you want to handle different states like loading and error.
const Example = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsError(false);
setIsLoading(true);
try {
const result = await fetch(url);
setData(result.features);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
};
fetchData();
}, []);
return (
<div>
{console.log(data)}
{data && <p>{JSON.stringify(data, null, 2)}</p>}
</div>
);
};

Categories

Resources