How to pass a function to React in State to sort your data based on multiple queries? - javascript

I am new to React and have a question regarding passing down functions in a state. I have a couple of 'sorting functions' in my util folder, which look like this:
export const sortColdestSummerCountries = (filteredCountries) => {
return filteredCountries.sort(
(a, b) => a.avsummertemp20802099 - b.avsummertemp20802099
);
};
and a few others:
sortHighestSummerTemp, sortLargestIncreaseHotDays, sortColdestSummerCountries, sortMostColdDays, sortWorstAffectedCountries which pretty much look similar. I use them to sort my data by users' request, and if I wrap sortHighestSummerTemp(filteredCountries) around my data, it works as a charm.
Now the issue: because I will have eventually 10+ filters, it makes sense to me to create a global state like this:
const onChangeQueryFilter = (e) => {
setQueryFilter(e.target.value);
};
Yet, upon trying queryFilter (filteredCountries) the terminal shows me "queryFilter is not a function" when I filter it? It's still the same sortHighestSummerTemp function right or what am I missing here? Do I summarize this problem correctly?
Hopefully it was clear and you understand what I am trying to do.
UPDATE:
I tried the following as well:
function sortBy(filteredCountries, sortKey) {
return [...filteredCountries].sort((a, b) => a[sortKey] - b[sortKey]);
}
// the queryFilter responds to the key, "filteredCountries.avsummertemp20802099" for example which passes to [sortKey].
const [queryFilter, setQueryFilter] = useState(filteredCountries.avsummertemp20802099);
const filteredData = sortBy(filteredCountries, queryFilter);
This does not work either, it still shows the old data (filteredCountries) but the filtering doesn't happen, the data isn't sorted. Does someone have a clue what I am doing wrong? Is it something in the sorting method? Or does someone have a better practice to pass a function to state?
Thank you a lot for being out here. :)

Related

RTKQ - Select data without hooks

I'm trying to select cached data from RTKQ without using the auto-generated query hook, but I'm having trouble understanding the docs
const result = api.endpoints.getPosts.select()(state)
const { data, status, error } = result
This is how the docs describe how to access the data, but I can't find any references on how to inject the state object "select()(state)".
I can't figure out how to access the data if I only call the select?
api.endpoints.getPosts.select()
Can someone explain me the difference between "select()" and "select()(state)"
Or what is the optimal solution to access the cached data from RTKQ?
The result of api.endpoints.getPosts.select() is a selector function for the result of using the "getPosts" endpoint without arguments.
Similarly, result of api.endpoints.getPosts.select({ page: 5 }) is a selector function for the result of using the "getPosts" endpoint the argument { page: 5 }.
A selector function is then called as selector(state) or passed into useSelector(selector).
If you write that altogether, you end up with api.endpoints.getPosts.select()(state).
#phry
Thank you for your answer! I'm not 100% sure I understood your answer. But it pointed me in a direction that enabled me to get the data.
I ended up creating a selector like the docs.
export const selectUser = (state) => userApi.endpoints.getUser.select()(state);
and in my function, I referenced it with getting the exported store from configureStore() method
const { data } = selectUser(store.getState());
But I'm not sure if this is the intended way to do it.

React component is re-rendering items removed from state

This is a difficult one to explain so I will do my best!
My Goal
I have been learning React and decided to try build a Todo List App from scratch. I wanted to implement a "push notification" system, which when you say mark a todo as complete it will pop up in the bottom left corner saying for example "walk the dog has been updated". Then after a few seconds or so it will be removed from the UI.
Fairly simple Goal, and for the most part I have got it working... BUT... if you quickly mark a few todos as complete they will get removed from the UI and then get re-rendered back in!
I have tried as many different ways of removing items from state as I can think of and even changing where the component is pulled in etc.
This is probably a noobie question, but I am still learning!
Here is a link to a code sandbox, best way I could think of to show where I am at:
Alert Component State/Parent
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/layout/PageContainer.js
Alert Component
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/parts/Alert.js
Any help much appreciated!
When you call a set function to update state, it will update from the last rendered value. If you want it to update from the last set value, you need to pass the update function instead of just the new values.
For instance, you can change your setTodos in your markComplete function to something like this.
setTodos(todos => todos.map((todo) => {
if (id === todo.id) {
todo = {
...todo,
complete: !todo.complete,
};
}
return todo;
}));
https://codesandbox.io/s/jovial-yalow-yd0jz
If asynchronous events are happening, the value in the scope of the executed event handler might be out of date.
When updating lists of values, use the updating method which receives the previous state, for example
setAlerts(previousAlerts => {
const newAlerts = (build new alerts from prev alerts);
return newAlerts;
});
instead of directly using the alerts you got from useState.
In the PageContainer.js, modify this function
const removeAlert = (id) => {
setAlerts(alerts.filter((alert) => alert.id !== id));
};
to this
const removeAlert = (id) => {
setAlerts(prev => prev.filter((alert) => alert.id !== id));
};
This will also fix the issue when unchecking completed todos at high speed

How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?

I just started using hooks in react and am creating a prototype custom hook for a framework.
The hook should take an object as an argument for initialization and cleanup (setting up/removing callbacks for example).
Here is my simplified Code so far:
export function useManager(InitObj) {
const [manager] = useState(() => new Manager());
useEffect(() => {
manager.addRefs(InitObj)
return () => manager.removeRefs(InitObj)
}, [manager]);
return manager;
}
to be used like this:
useManager({ cb1: setData1, cb2: setData2... })
In future Iterations the Manager might be a shared instance, so I need to be able to be specific about what I remove upon cleanup.
I put console.log all over the place to see If i correctly understand which code will be run during a render call. From what I can tell this code does 100% what I expeted it to do!
Unfortunately (and understandably) I get a warning because I did not include InitObj in the effects dependencies. But since I get an object literal simply putting it in there will cause the effect to be cleaned up/rerun on every render call since {} != {} which would be completely unnecessary.
My research so far only revealed blog posts like this one, but here only primitive data is used that is easily classified as "the same" (1 == 1)
So far I have found 3 possible solutions that I am not completely happy with:
using useMemo to memoize the object literal outside the hook
useManager(useMemo(() => { cb: setData }, []))
This adds more responsibility on the developer using my code => not desirable!
using useState inside the hook
const [iniOBj] = useState(InitObj);
A lot better already, but it adds state that does not feel like state. And it costs (minimal) execution time and memory, I would like to avoid that if possible.
using // eslint-disable-next-line react-hooks/exhaustive-deps
Works for sure, but there might still be other dependencies that might be missed if I simply deactivate the warning.
So my question is:
How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?
I half expect that the useState option will be my best choice, but since I am new to hooks there might still be something that eluded my understanding so far.

How to Sort/Filter Firebase Realtime Database with Query in Javascript

I'm having a hard time understanding why this simple query is not giving me the expected results.
In my Firebase realtime database my structure looks like this:
Pretty simple, right? Next I have a cloud function that runs whenever I manually change one of the orbs values under any of the users. This is my function:
exports.testTopPlayers2 = functions.database.ref('/TestUsers/{user}/Statistics').onUpdate(_ => {
const allPlayersRef = admin.database().ref('/TestUsers');
const query = allPlayersRef.orderByChild('orbs').limitToLast(2)
return query.on('value', (snap, context) => {
return console.log(snap.val());
})
Also very straightforward I would think. In regular english, what I'm expecting that function to do is: "When any of the orbs values are changed, run this function to sort all of the users by their orbs value (lowest to highest) and take a new DataSnapshot of only the final 2 users' data (The users with the highest number of orbs)".
When I print to the console, I'm expecting to see the data from User3 & User6 because they have the highest number of orbs... but instead it seems to be sorting by the username (User5 & User6) or simply not sorting at all. Here is the log output:
Which clearly does not sort by what I'm defining in my query.
Maybe I'm missing something obvious but I've stared at it long enough... hoping someone can spot a mistake in my function regarding how it's sorting.
I appreciate the help!
You're looking for:
allPlayersRef.orderByChild('Statistics/orbs').limitToLast(2)
Note that you'll also want to use once() instead of on():
return query.once('value').then((snap) => {
return snap.val();
})
You'll also want to remove that console.log around snap.val(), since console.log() doesn't return anything.
Finally: when you call .val() on a snapshot, you're converting it to key-value pairs and lose the ordering information. If you want to maintain the ordering, you'll want to use forEach:
return query.once('value').then((snap) => {
let results = [];
snap.forEach((player) => {
results.push( {key: snap.key, ...snap.val() }
});
return results;
})
Thanks to Frank's thorough answer, I was able to understand what needs to happen and got it working perfectly with minor tweaks.
With the exact modifications suggested by Frank, my output in the Firebase console looked like this:
Which is a lot better than what I was able to produce until now, but it still wasn't showing the exact data I wanted to use.
I wasn't interested in the key of the snap (which is the top-level TestUsers node), but rather I wanted the key of the player which provides the username responsible for the high score.
Next, Instead of getting an [object] for the orb values, I needed the actual value of the orbs, so I had to dive into the children of each player object to get that value.
This is my tweaked cloud function in all it's glory lol:
exports.testTopPlayers2 = functions.database.ref('/TestUsers/{user}/Statistics').onUpdate(_ => {
const allPlayersRef = admin.database().ref('/TestUsers');
const query = allPlayersRef.orderByChild('/Statistics/orbs').limitToLast(2)
return query.once('value').then((snap) => {
let results = [];
snap.forEach((player) => {
let username = player.key
let orbsValue = player.child("Statistics").child("orbs").val()
results.push( {Player: username, Score: orbsValue } )
});
console.log(results)
return results;
})
})
And the result of the Firebase log is this:
This is exactly what I was after and can now proceed :D

GraphQL Authentication with React and Apollo

I'm currently following this guide (https://www.youtube.com/watch?time_continue=1614&v=_kYUzNVyj-0) to implement authentication in my react, graphql and apollo application. In the guide they have this function:
async function context(headers, constants) {
console.log('calling context') // This never prints, so it's not being called.
const user = await getUser(headers['authorization'])
return {
headers,
user
}
}
Below that function I have my resolvers and mutation for the GraphQL. One of the queries is supoosed to get the current user and that looks like this:
export default {
Date: GraphQLDate,
Query: {
currentUser: (root, args, { user }) => {
console.log(user) // undefined
console.log('currentUser')
return hello
},
...
In the video they later access the values via object destructuring, or just importing the whole object in their arguments:
When I'm trying to access anything from the return object from the context function I get undefined. It's due to the fact that the function is never being called, and I don't know why. It's probably something dumb I'm missing here.
I made a Gist of the code that I want to get working if that might help my shining knight that will hopefully save my day.
https://gist.github.com/Martinnord/6d48132db9af1f9e7b41f1266444011c
I can add more context to my question if anyone like! Thanks so much for reading!
You need to export your context function for Launchpad to pick it up. So
export async function context(headers, constants) {
console.log('calling context') // This never prints, so it's not being called.
const user = await getUser(headers['authorization'])
return {
headers,
user
}
}
It's not fully clear IMO that launchpad is looking for that context function. This is a tricky topic as well since the context function isn't required to make things work it doesn't notify you that it isn't there.

Categories

Resources