Nested data in RTKQuery hook getting join data - sql

I am implementing the "favorites" logic in a project where there are some cards showing some info about songs, and a heart icon shows red if that song is in the user favorites or empty if it is not.
I am using Supabase as a backend (SQL) and I already have a "profiles" table, a "songs" table and a "favorites" table with two foreign keys: profile_id and song_id.
In the project I am using RTKQuery and I have already configured the endpoint to obtain the data for the favorites which is like:
endpoints: (builder) => ({
getFavorites: builder.query({
queryFn: async (id) => {
const {data, error} = await supabase
.from('favorites')
.select(`users_id,songs_id (*)`)
.eq('users_id',id)
return { data, error }
},
providesTags: ["Favorites"]
}),
Then I get the data with:
const { data, isSuccess } = useGetFavoritesQuery( id );
I am calling this hook from the component mad for each card, which already have the props. So I can check if the prop "song.id" is in some of the ids inside the "favorites" object.
But the problem I am facing is because the data that I really need is the nested object inside data, and each one of the nested objects have the id that I would use to check if it is === to song.id of the component I am calling from.
I have tried several things but I don't know if I am doing fine. This is one solution that I have tried. First I iterate over data to obtain the object "favorites":
let favorites;
if (isSuccess){
data.forEach(element => {
favorites = element.songs_id;
})
}
And then the boolean for the heart red or empty:
const favoriteIconColor = useMemo(() => {
if (Object.values(favorites).some(item=> item.id === song.id)){
return true
}
} , [favorites, song]);
But that is not working. I am thinking about creating just an array with all of the ids in the first iteration, but I am not sure if I am overcomplicating the issue.
I have also considered that maybe there is a way to obtain the data as I need with the query itself, but not sure about it. Any advice?

Related

Is it okay to modify some state in Redux if after we modify it we call an action to overwrite the old state?

OK, say I have an initial state in our Redux store that looks like this:
const initialState = {
userReports: [],
activeReport: null,
}
userReports is a list of reports. activeReport is one of those reports (the one that is actively being worked with).
I want the active report to point to one in the array. In other words, if I modify the active report, it would modify one in the userReports array. This means, the two objects must point to the same memory space. That's easy to set up.
The alternative to this approach would be to copy one of the reports that is in the userReports array and set it as the active report (now it has a different memory address). The problem is now, when I edit the activeReport, I also have to search through the array of userReports, find the report that resembles the active report and modify it there too. This feels verbose.
Here is the question:
Would it be bad practice to have the activeReport point to a report in the array (same object). When I want to change the report I could do something like this (example is using redux thunk):
export const updateReport = (report) => async (dispatch, getState) => {
try {
const report = getState().reports.activeReport
// modify the active report here
report.title = "blah blah blah"
dispatch({ type: ACTIONS.UPDATE_REPORT, payload: report })
} catch (error) {
console.log(`ERROR: ${error.message}`)
}
}
And in my reducer:
case ACTIONS.UPDATE_REPORT:
return { ...state, activeReport: action.payload }
as you can see, after updating the report I still return a "new version" of that report and set it as active, but this approach also updates the report in the userReports array because they point to the same memory address.
I would say thats not ideal, do the reports have id's? If they do I would rather hold the userReports in an object with keys being the id's, then active report can just be an id and renamed to activeReportId so you can fetch the activeReport with userReports[activeReportId]
You also asked for reasons:
So firstly any screen that looks at userReports wont rerender because the reports aren't being reassigned.
Secondly if someone later wants to update those screens they will reassign userReports which could cause problems.
Thirdly its an unusual pattern which is a huge no no for redux. The point of redux is that it has a very obvious pattern so when you add things to it you don't have to think and can just make changes with confidence.
Your activeReport should not be pointing to an object in the userReports array, but rather it should be an id of the report, which the user is currently working on. Each of the report in the userReports will have a unique id field to identify the report - this would be helpful when rendering in react - this id field can be used as key.
Then your action creator/dispatcher will look like this:
export const updateReport = (updatedReport) => async (dispatch, getState) => {
dispatch({ type: ACTIONS.UPDATE_REPORT, payload: updatedReport });
}
You will call this on change in your component:
const onTitleChangeHandler = (e) => {
var newTitle = e.target.value;
// you will get the userReports and activeReport from props or by using some redux selector, also you will need to get dispatch and getState from redux
var activeReportObj = userReports.filter((r) => r.id === activeReport)[0];
updateReport({ title: newTitle, ...activeReportObj })(dispatch, getState);
}
Lastly, your reducer will be:
case ACTIONS.UPDATE_REPORT:
var newUserReports = state.userReports.map((r) => {
if (r.id === state.activeReport) {
return action.payload;
}
return r;
});
return { newUserReports, ...state };

API multiple requests with axios / Vue.js, advice on doing things the smarter way

First of: I'm a beginner at Vue.js/APIs so I hope my question is not too stupid (I may not be seeing the obvious) :)
So,
Using Vue.js I'm connecting to this API and want to track the history of each crypto-currencies (no issues with getting any data from the API).
Currencies information are accessible using a URL :
https://api.coinranking.com/v2/coins
And history is accessible using another :
https://api.coinranking.com/v2/coin/ID_OF_THE_COIN/history
As you can see the second url needs the id of the specific currency which is available in the first one.
I would like to find a way to make only 1 get request for all currencies and their history rather than having to make as many requests as available currencies there are (about 50 on this API), I've tried several things but none has worked yet (for instance using the coin url and storing ids of the currencies in a table then using the history url and modifying it with the ids of the table but hit a wall) .
Here's the axios get request I have for the moment for a single currency:
const proxyurl = "https://cors-anywhere.herokuapp.com/"
const coins_url = "https://api.coinranking.com/v2/coins"
const history_url = "https://api.coinranking.com/v2/coin/Qwsogvtv82FCd/history"
//COINS DATA
axios
.get(proxyurl + coins_url, {
reqHeaders
})
.then((reponseCoins) => {
// console.log(reponseCoins.data)
this.crypto = reponseCoins.data.data.coins;
})
.catch((error) => {
console.error(error)
})
//GET ALL COINS UUIDs
axios
.get(proxyurl + coins_url, {
reqHeaders
})
.then((reponseUuid) => {
this.cryptoUuidList = reponseUuid.data.data.coins;
//access to each crypto uuid:
this.cryptoUuidList.forEach(coinUuid => {
console.log("id is: " + coinUuid.uuid)
//adding uuids to table:
this.coinsUuids.push(coinUuid.uuid);
});
})
.catch((error) => {
console.error(error)
})
// COIN HISTORY/EVOLUTION COMPARISON
axios
.get(proxyurl + history_url, {
reqHeaders
})
.then((reponseHistory) => {
//get data from last element
const history = reponseHistory.data.data.history
this.lastItem = history[history.length-1]
// console.log(this.lastItem)
this.lastEvol = this.lastItem.price
// console.log(this.lastEvol)
//get data from previous element:
this.previousItem = history[history.length-2]
this.previousEvol = this.previousItem.price
})
.catch((error) => {
console.error(error)
})
I probably forgot to give some info so let me know and will gladly share if I can
cheers,
I took a look at the API, they do not seem to give a way for you to get everything you need in one request so you will have to get each coin history separately.
However, I do se a sparkline key in the returned data, with what seems to be a few of the latest prices.
I do not know your projects's specifics but maybe you could use that for your initial screen (for example a coins list), and only fetch the full history from the API when someone clicks to see the details of a coin.

How to map some data to the data fetched from API before showing it on screen?

I am new to React Native. I am trying to make an app of my own to try out the different things that I learnt and also get to know new things and one such thing that I came across and is giving me a hard time is the following issue:
I have an API which gives me certain data about an item. The properties of the item are listed in the API like "sizeofitem" , "nameofitem" or "itemacategory". Now there are multiple items for different items and not all properties are present in each item. What I was trying to achieve is to somehow map these properties in the following manner:
If let's say "sizeofitem", should become "Size of Item", "nameofitem" should become "Name of Item". Now these properties are different of all the items so for example, sizeofitem might be in one item detail list but might not be in another, but I have all the properties that are can be there. Can someone help me how to do this?
Till now I have the following:
const [itemDtl , setItemDtl] = useState([]);
const getItemInfo = async (id) => {
try{
const response = await api.get(`myAPI/${id}`);
setItemDtl(response.data.obj.itemutils);
}catch(err){
console.log(err);
}
}
let arr = [];
for(let i in itemDtl){
arr.push(itemDtl[i].util_type);
}
console.log(arr);
useEffect(() => {
getItemInfo(id);
})
arr array has whatever the properties where listed for the item in the API i.e. [sizeofitem, nameofitem , etc].
I want an array to have [Size of Item, Name of Item , etc].
Basically just, to sum up, I want to rename the list of properties that can be there for when whatever property comes up is then stored in an array with the mapped string I have given, so for example if an item has 'sizeofitem : 50', I want it to be stored as "Size of item" so that I can show that on the screen. And there are like a total of 5 properties that can exist for an item so I can code it somewhere maybe like sizeofitem : 'Size Of Item' so that when sizeofitem property is top be shown on the screen I can use this and show Size of Item on the screen.
try this:
const [itemDtl , setItemDtl] = useState([]);
const getItemInfo = async (id) => {
try{
const response = await api.get(`myAPI/${id}`);
let arr = [];
for(let i in itemDtl){
arr.push(itemDtl[i].util_type);
}
setItemDtl(arr);
}catch(err){
console.log(err);
}
}
useEffect(() => {
getItemInfo(id);
})

Vuexfire bindFirebaseRef with pagination infinite scroll

Question: How can I add pagination (infinite scroll) to my binded Firestore VuexFire reference without re-querying previously retrieved (and binded) results?
Background:
I am currently using VuexFire firestore binding to fill a timeline for most upvoted posts, as an action, in my Vuex store like this:
fillTimeLine: firebaseAction(
({ bindFirebaseRef }) => {
bindFirebaseRef(
'timelineResults',
db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.limit(30)
)
})
This will retrieve the top 30 highest rated posts in my firestore database to my vuex state variable timelineResults.
To add pagination I have found a non-VuexFire example like this:
How to paginate or infinite scroll by number of items in firestore?
var first = db.collection("....").orderBy("price", "desc").limitTo(20);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("....")
.orderBy("price", "desc")
.startAfter(lastVisible)
.limit(20);
});
Is there a way to combine the two examples and append results to a binded reference?
You could create a more generic action, just like this:
bindRef: firestoreAction(({ bindFirestoreRef }, { name, ref }) => {
bindFirestoreRef(name, ref);
}),
And then using it like:
this.bindRef({
name: 'timelineResults',
ref: db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.limit(30),
});
There you can change the ref according to your needs. In this case, when you detect the scroll limit:
// lastVisible: using the array position from the previous binding
// since with vuex's bound data you cannot get the snapshots
this.bindRef({
name: 'timelineResults',
ref: db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.startAfter(lastVisible)
.limit(20),
});

How to populate the store and sequentially await return using Redux Observable?

I am attempting to use Redux Observable to call an action to fetch some data, wait for its return, then fetch some more data that relies on it.
I have an epic which populates a store from a fetch FetchTodos. This listens for the FETCH_TODOS action and then calls my todos API and populates {todos: [] } =
I also have a comments section in my store todoComments. However, I would like to only populate todoComments once FETCH_TODOS has returned and populated the store.
In imperative code, this might look like:
let todos = await api.get('/todos');
await dispatch("FETCH_TODO_COMPLETE", todos)
let firstId = getState().todos[0].id
let comments = await api.get(`/todos/${firstId}/comments')
await dispatch("FETCH_COMMENTS_COMPLETE", { todo_id: firstId, comments})
The closest I saw to this was this issue in the Redux Observable Repo, but I could not understand how to do this efficiently. This is a pretty common scenario for me.
I would like to reuse as much code as possible. In this example, I may dispatch FETCH_TODOS from multiple components.
How would i accomplish this with Redux-Observable?
Based on our conversation in the comments:
In redux-observable, you can sequence things in numerous ways. You could do it all in one epic using normal RxJS, or you could split them into multiple ones. If you split them, the subsequent epic would listen for the signal that the previous one has completed its task. Something like this:
// this assumes you make your `api.get` helper return an Observable
// instead of a Promise which is highly advisable.
// If it doesn't, you could do:
// Observable.from(api.get('/url'))
// but Promises are not truly cancellable which can cause max
// concurrent connections issues
const fetchTodosEpic = action$ =>
action$.ofType('FETCH_TODOS')
.switchMap(() =>
api.get('/todos')
.map(todos => ({
type: 'FETCH_TODOS_COMPLETE',
todos
}))
);
const fetchComments = action$ =>
action$.ofType('FETCH_TODOS_COMPLETE')
.switchMap(({ todos }) =>
api.get(`/todos/${todos[0].id}/comments`)
.map(comments => ({
type: 'FETCH_COMMENTS_COMPLETE',
comments
}))
);