React save api requests - reactjs

I am building a small online shopping web app and I have about 10 categories of products e.g. Mens, Womens, Kids etc. I want to memoize the requests. My question is how do I do it? I read about useCallback hook and I believe that I should use it here, but at the same time since there are like 10 categories how do I keep track for which category should I get from the cache and for which should I make the request? I have just considering declaring useState hooks for each category and change state for each depending on selected category.
const [productsMensCategory, setProductsMensCategory] = useState([]);
const [productsWomensCategory, setProductsWomensCategory] = useState([]);
const [productsKidsCategory, setProductsKidsCategory] = useState([]);
I have also been thinking that Redux might be useful here.
Could anyone please guide me a bit here, I would like to know how to approach this.
This is how I make a request.
const [products, setProducts] = useState([]);
useEffect(() => {
const getProducts = async (category: string) => {
try {
const response = await getCategoryProducts(category);
console.log(response);
if (response.status === 200) {
setProducts(response.data);
}
} catch (e) {
console.log(e);
}
};
getProducts(handleChangeCategory(section));
}, [section, setProducts]);

If you want these to stay saved even if you reload page you could use localStorage to make it easy. Redux would be a nice approach to not make another request, but as far as you reload it won't save state.
UseCallback only memorizes a callback, so if re-render happens this callback remains the same, it only changes if a dependency from its dependencies array changes

Good idea to have the data memoized. In my opinion, if at all you can get the data with flags based on date_modified for each category (as server response) you can then set or not set component state.
Something like below
if (response.status === 200 && category_flag_from_server !== current_category_flag_from_state) {
setProducts(response.data);
}

Related

useLazyQuery in Promise.all only storing last response in cache

I'm trying to use the same query with different variables using useLazyQuery. I built a hook for this reason and it fetches everything alright. However, it never really uses cached data. I looked into the cache and it's only storing the response from the last request in the array. This is a simplified version of my hook:
const useProductSearchQuery = (categories) => {
const [getProducts] = useLazyQuery(QUERY);
const [data, setData] = useState();
useEffect(() => {
async function getProducts() {
const responses = await Promise.all(
categories.children.map((cat) =>
getProducts({ variables: { category: cat.id } })
)
);
setData(responses);
}
getProducts();
}, [productCategories, getProducts]);
return { data };
};
I'm not sure if this use case fits useLazyQuery so maybe that's why it doesn't work. I just needed an imperative way of running my queries and this seemed easier than using a consumer to pass the client around.
The other alternative would be to just iterate categories and then have a useQuery hook for each but I'd prefer having my data ready in one go.
Nevermind I didn't notice the hooks docs mention the useApolloClient hook (https://www.apollographql.com/docs/react/api/react/hooks/#useapolloclient)... Works like a charm!

Refreshing data received from api in React best practice

I am new to React and I am working on a simple project wherein I have an api with some data where that data changes every 2-3 seconds. Till now I was doing something like this :
const [Data, setData] = useState([]);
const[Data,setData] = useState([])
useEffect(() => {
fetch('api-url').then((result) => {
result.json().then((resp) => {
setData(resp);
setLoading(false)
})
})
}, [Data])
This was working fine for me as the data displayed on the webpage was also refresing automatically everytime the data changed in api also on removing Data from dependency it wouldnt update automatically.
I wanted to know whether it is a good practice or even a working method(data might be updating due to some other thing) to get new data . Also some part of my code that is related to this data(Eg. in one component I send the data that matches the user search) in that component the data does not update automatically so how do I solve that.
function handleAddFavourite(name) {
const newFav = Object.values(Data).find(e => e.name === name)
if (favourites.find((n) => n.name === name)) {
const newFavList = favourites.filter((n) => n.name !== name)
setFavourites(newFavList)
}
else {
const newFavList = [...favourites, newFav];
setFavourites(newFavList)
console.log(newFavList)
}
}
Here the add to fav. functionality works correctly. I send this fav list to another component . But in that component the values in data doesnt update automatically even when they are being updated in api data.
Any help would be appreciated.

React hook useCallback with get API call inside useEffect not behaving correctly

I want to achieve the following.
Action from redux is imported in this component. It fetches data from firebase, which I use as a database. Data is displayed inside this component.
My problem is that data is fetched every time component is mounted, which is not an ideal scenario.
I would like to prevent that behavior.
Ideal scenario is that data should be fetched only first time component is rendered and no more afterwards. Of course it should also fetch when data is changed otherwise no. I used useCallback, but certainly there is a mistake in the code below. Token and Id are passed when user is authenticated.
import { fetchFavs } from "../../store/actions";
import { useSelector, useDispatch } from "react-redux";
const token = useSelector((state) => state.auth.token);
const id = useSelector((state) => state.auth.userId);
const fetchedFavs = useSelector((state) => state.saveFavs.fetchedFavs);
const dispatch = useDispatch();
const fetchFavsOptimized = useCallback(() => {
dispatch(fetchFavs(token, id));
console.log("rendered");
}, [token, id, dispatch]);
useEffect(() => {
if (token) {
fetchFavsOptimized();
}
}, [fetchFavsOptimized, token]);
There are two ways to handle this, one explicit and one implicit.
The explicit way would be to add a new boolean item in your redux store and check against that every time you load your component to see if you should load the data from the db or not.
So your redux store may have a new key/value: dataLoaded: false;
In your component now you need to load this from your redux store and check against it in your use effect:
const dataLoaded = useSelector((state) => state.dataLoaded);
useEffect(() => {
if (token && !dataLoaded) {
fetchFavsOptimized();
}
}, [fetchFavsOptimized, token, dataLoaded]);
Don't forget in your fetchFavsOptimized function to update the dataLoaded to true after the loading of the data from the db is complete.
This has the upside that every time you want to load the db from the db again you simply need to set dataLoaded to false.
The implicit way is to check the data length and if that is 0 then execute the load. Assuming that you store data in your redux store in this form: data: [] and your data is an array (you could do the same thing by initiating data as null and check if data !== null) your component would look like this:
const data = useSelector((state) => state.data);
useEffect(() => {
if (token && data.length === 0) {
fetchFavsOptimized();
}
}, [fetchFavsOptimized, token, data]);
Keep in mind that I wouldn't do it like that because if you don't have any data you may end up with an infinite loop, but it's a good technique to use in other cases.
For fetching only once on component mounting: (mind [])
React.useEffect(() => {
fetchFavsOptimized()
}, []) // <- this empty square brackets allow for one execution during mounting
I'm not sure, but the issue of continous calling of useEffect might be [fetchFavsOptimized, token]. Placing fetchFavsOptimized function in [] might not be the best way.
You want to fetch, when data changes, but what data? You mean token ? Or something else?
React.useEffect() hook alows for fetching when data changes with placing this changable data in [here_data] of useEffect. Example:
const [A, setA] = React.useState();
React.useEffect(() => {
// this block of code will be executed when state 'A' is changed
},[A])
The best practise of using useEffect is to separate the conditions in runs with.
If You need to fetch on some data change AND on component mount, write TWO useEffect fuctions. Separate for data change and component mount.

How to Avoid Fetching Data Twice When Using React Hooks

I'm trying to fetch a set of questions from a Firestore database, and then store them in a local array for use later on in a quiz. As I'm working to fetch the data, it's running asynchronously. I've dealt with this before, and the solution was a useEffect() hook and conditionally rendering component.
My code currently looks like this:
var questions = [];
var collection = useRef('Planning');
var [loading, setLoading] = useState(true);
useEffect(() => {
if (props.track === 'Testing & Deployment') {
collection.current = 'Test_Deploy';
} else {
collection.current = props.track;
}
if(questions.length !== 0){
setLoading(false)
} else {
var questionSet = db.collection('Quizzes').doc(collection.current).collection('Questions')
questionSet.withConverter(questionConverter).get().then(function(response) {
response.forEach(document => {
var question = document.data();
// running asynch - need to address
console.log('Question in')
questions.push(question)
})
})
setLoading(false)
}
}, [props.track, questions])}
Depending on where setLoading() is, the component either won't re-render at all, or will double fetch the data from Firestore. I've tried playing around with useCallback() to no avail, and have also seen that this might be an issue with React StrictMode.
Any advice on how to fix my code in order to re-render the page after fetching all of the questions, without double fetching them?
To fetch data once on component mount you can specify an empty dependency array. I also suggest moving the questions object into local component state. Since the state is updated in a loop you should use a functional state update to add each new question to the array.
const [questions, setQuestions] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const questionSet = db
.collection("Quizzes")
.doc(collection.current)
.collection("Questions");
questionSet
.withConverter(questionConverter)
.get()
.then(function (response) {
response.forEach((document) => {
const question = document.data();
setQuestions(questions => [...questions, question]);
});
});
setLoading(false);
}, []);
If the linter complains about missing dependencies then you need to either add them and place the appropriate conditional check on the data fetch, or add a comment to ignore/disable the linter for the line with the dependency array, // eslint-disable-next-line react-hooks/exhaustive-deps.
It will rerender again when the props change or update including props.track and questions everytime when you push item into it. So if you want to get rid of it rerender then just remove from useEffect like
useEffect(() => {
.......
}, [loading])} // remove from here and add `loading` here just

In React, fetch data conditional on results of an initial fetch

We have written a custom data fetching hook useInternalApi which is similar to the useDataApi hook at the very bottom of this fairly decent tutorial on data fetching with react hooks. Our app fetches a lot of sports data, and in particular, we are trying to figure out the right data-fetching pattern for our use case, which is fairly simple:
Fetch general info for a specific entity (an NCAA conference, for example)
Use info returned with that entity (an array of team IDs for teams in the specific conference), and fetch info on each team in the array.
For this, our code would then look something like this:
import `useInternalApi` from '../path-to-hooks/useInternalApi';
// import React... and other stuff
function ComponentThatWantsTeamInfo({ conferenceId }) {
// use data fetching hook
const [conferenceInfo, isLoading1, isError1] = useInternalApi('conferenceInfo', { conferenceId: conferenceId })
// once conferenceInfo loads, then load info from all teams in the conference
if (conferenceInfo && conferenceInfo.teamsArray) {
const [teamInfos, isLoading2, isError2] = useInternalApi('teamInfo', { teamIds: conferenceInfo.teamIds })
}
}
In the example above, conferenceId is an integer, teamIds is an array of integers, and the combination of the 2 parameters to the useInternalApi function create a unique endpoint url to fetch data from. The two main problems with this currently are:
Our useInternalApi hook is called in an if statement, which is not allowed per #1 rule of hooks.
useInternalApi is currently built to only make a single fetch, to a specific endpoint. Currently, it cannot handle an array of teamIds like above.
What is the correct data-fetching pattern for this? Ideally, teamInfos would be an object where each key is the teamId for one of the teams in the conference. In particular, is it better to:
Create a new internal hook that can handle an array of teamIds, will make the 10 - 20 fetches (or as many as needed based on the length of the teamsArray), and will use Promise.all() to return the results all-together.
Keep the useInternalApi hook as is, and simply call it 10 - 20 times, once for each team.
Edit
I'm not sure if the underlying code to useInternalApi is needed to answer this question. I try to avoid creating very long posts, but in this instance perhaps that code is important:
const useInternalApi = (endpoint, config) => {
// Set Data-Fetching State
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [isError, setIsError] = useState(false);
// Use in lieu of useEffect
useDeepCompareEffect(() => {
// Token/Source should be created before "fetchData"
let source = axios.CancelToken.source();
let isMounted = true;
// Create Function that makes Axios requests
const fetchData = async () => {
// Set States + Try To Fetch
setIsError(false);
setIsLoading(true);
try {
const url = createUrl(endpoint, config);
const result = await axios.get(url, { cancelToken: source.token });
if (isMounted) {
setData(result.data);
}
} catch (error) {
if (isMounted) {
setIsError(true);
}
} finally {
if (isMounted) {
setIsLoading(false);
}
}
};
// Call Function
fetchData();
// Cancel Request / Prevent State Updates (Memory Leaks) in cleanup function
return () => {
isMounted = false; // set to false to prevent state updates / memory leaks
source.cancel(); // and cancel the http request as well because why not
};
}, [endpoint, config]);
// Return as length-3 array
return [data, isLoading, isError];
};
In my opinion, if you need to use a hook conditionally, you should use that hook inside of a separate component and then conditionally render that component.
My understanding, correct me if I'm wrong, is that the initial API call returns an array of ids and you need to fetch the data for each team based on that id?
Here is how I'd do something of that sorts.
import `useInternalApi` from '../path-to-hooks/useInternalApi';
// import React... and other stuff
function ComponentThatDisplaysASpecificTeam(props){
const teamId = props.teamId;
const [teamInfo] = useInternalApi('teamInfo', { teamId });
if(! teamInfo){
return <p>Loading...</p>
}
return <p>do something with teamInfo...</p>
}
function ComponentThatWantsTeamInfo({ conferenceId }) {
// use data fetching hook
const [conferenceInfo, isLoading1, isError1] = useInternalApi('conferenceInfo', { conferenceId: conferenceId })
if (! conferenceInfo || ! conferenceInfo.teamsArray) {
return <p>this is either a loading or an error, you probably know better than me.</p>
}
// Let the data for each team be handled by its own component. This also lets you not have to use Promise.all
return (
<div>
{conferenceInfo.teamIds.map(teamId => (
<ComponentThatDisplaysASpecificTeam teamId={teamId} />
))}
</div>
)
}

Resources