How can I groupby in React/Redux code (new to frontend)? - arrays

I am having difficulty with this function to run in my React/Redux code. I am fairly new to React (mostly work on backend) so I am not sure why it's not running as expected. Is it in the right place? I am also having trouble finding where console.log is printing in the console, since the console is set up with prev action, action, next state patterns....
I have this function defined in my 'actions.js' (where it is also called later on):
const groupBy = (array, key) => {
return array.reduce((result, currentValue) => {(
result[currentValue[key]] = result[currentValue[key]] || []).push(
currentValue
);
return result;
}, {});
};
Here is where the function is called (same file):
export function getAlerts() {
return dispatch => {
api.get('notifications', (response) => {
const grouped = groupBy(response.results.data, 'email');
dispatch(updateFetchedAlerts(grouped));
}, (error) => {
console.warn(error);
})
}
}
The input, response.results.data, looks something like this:
[{"email": test#email.com, "is_active": true, "alert_id": 1, "pk": 1},
{"email": test#email.com, "is_active": true, "alert_id": 2, "pk": 2},
{"email": different#email.com, "is_active": true, "alert_id": 1, "pk": 3}]
I want it to look like this:
[{"test#email.com": [{"is_active": true, "alert_id": 1, "pk": 1},
{"is_active": true, "alert_id": 2, "pk": 2}],
"different#email.com": [{"is_active": true, "alert_id": 1, "pk": 3}]}]
but it seems to not be running this function, I've rerun yarn build and used incognito...
UPDATE: This function actually WORKS! Thanks all. The redux developer tools are very helpful. Now the second problem is I need to add in my own keys.... So ideally the result would look like this. Preferably no lodash!:
[{"email": "test#email.com",
"alerts": [{"is_active": true, "alert_id": 1, "pk": 1},
{"is_active": true, "alert_id": 2, "pk": 2}]},
{"email": "different#email.com",
"alerts": [{"is_active": true, "alert_id": 1, "pk": 3}]}]

You can take the same function you used to group by which yields object of shape
{ [string]: object[] }
Using Object.entries allows you to convert this to an array of key-value pairs that you can map to an array of objects with shape
{
email: string,
alerts: object[],
}
Function updates
const groupBy = (array, key) =>
array.reduce((result, { [key]: k, ...rest }) => {
(result[k] = result[k] || []).push(rest);
return result;
}, {});
const groupDataBy = (array, key) =>
Object.entries(groupBy(array, key)).map(([email, alerts]) => ({
email,
alerts
}));
The map function callback ([email, alerts]) => ({ email, alerts }) uses array destructuring to assign the array of [key, value] to named variables email and alerts, and object shorthand notation to create an object with keys named for the variables.
const data = [{
"email": "test#email.com",
"is_active": true,
"alert_id": 1,
"pk": 1
},
{
"email": "test#email.com",
"is_active": true,
"alert_id": 2,
"pk": 2
},
{
"email": "different#email.com",
"is_active": true,
"alert_id": 1,
"pk": 3
}
];
const groupBy = (array, key) =>
array.reduce((result, { [key]: k, ...rest }) => {
(result[k] = result[k] || []).push(rest);
return result;
}, {});
const groupDataBy = (array, key) =>
Object.entries(groupBy(array, key)).map(([email, alerts]) => ({
email,
alerts
}));
const res = groupDataBy(data, 'email');
console.log(res)

Ok, first the console is in the Browser Developer Tools seccion console. There you can view all the console.log. In Chrome you can open it by pressing F12.
And the other question, the function that you`re needing is:
//Function groupBy.
function groupBy(array, key) {
let arrayReduced = array.reduce(
(result, { [key]: k, ...rest }) => {
(result[k] = result[k] || []).push(rest);
return result;
},
{}
);
return arrayReduced;
}
//Your example data.
const group = [
{
email: 'test#email.com',
is_active: true,
alert_id: 1,
pk: 1,
},
{
email: 'test#email.com',
is_active: true,
alert_id: 2,
pk: 2,
},
{
email: 'different#email.com',
is_active: true,
alert_id: 1,
pk: 3,
},
];
//Function executed
const result = groupBy(group, 'email');
//Result
console.log(result);
I hope it helps!

Related

how to refactoring $expr, $regexMatch filter for easier reading React/MongoDB?

I would like to explain my problem of the day.
Currently I perform a filter on an input which allows me to search the last name and first name it works really well
I have deleted a lot of things for a simpler reading of the code if there is a need to bring other element do not hesitate to ask
const {
data: packUsersData,
} = useQuery(
[
"pack",
id,
"users",
...(currentOperatorsIds.length ? currentOperatorsIds : []),
value,
],
async () => {
const getExpr = () => ({
$expr: {
$or: [
{
$regexMatch: {
input: {
$concat: ["$firstName", " ", "$lastName"],
},
regex: value,
options: "i",
},
},
{
$regexMatch: {
input: {
$concat: ["$lastName", " ", "$firstName"],
},
regex: value,
options: "i",
},
},
],
},
});
let res = await usersApi.getrs({
pagination: false,
query: {
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
$or: value
? [
{
entities: [],
...getExpr(),
},
{
entities: { $in: id },
...getExpr(),
},
]
: [
{
entities: [],
},
{
entities: { $in: id },
},
],
},
populate: "entity",
sort: ["lastName", "firstName"],
});
{
refetchOnMount: true,
}
);
and so i find the read a bit too long have any idea how i could shorten all this?
thx for help.
You can reduce entities field $or condition, just concat the empty array and input id,
let res = await usersApi.getrs({
pagination: false,
query: {
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
entities: { $in: [[], ...id] },
...getExpr()
},
populate: "entity",
sort: ["lastName", "firstName"]
});
If you want to improve the regular expression condition you can try the below approach without using $expr and aggregation operators,
create a function and set input searchKeyword and searchProperties whatever you want to in array of string
function getSearchContiion(searchKeyword, searchProperties) {
let query = {};
if (searchKeyword) {
query = { "$or": [] };
const sk = searchKeyword.trim().split(" ").map(n => new RegExp(n, "i"));
searchProperties.forEach(p => {
query["$or"].push({ [p]: { "$in": [...sk] } });
});
}
return query;
}
// EX:
console.log(getSearchContiion("John Doe", ["firstName", "lastName"]));
Use the above function in query
let res = await usersApi.getrs({
pagination: false,
query: Object.assign(
{
"roles.name": "operator",
_id: { $nin: currentOperatorsIds },
deletedAt: null,
entities: { $in: [[], ...id] }
},
getSearchContiion(value, ["firstName", "lastName"])
},
populate: "entity",
sort: ["lastName", "firstName"]
});

Global state with redux somehow switches variables

I am very confused as to why this is happening as this has never happened with me before using redux. I am building a react native application and currently when I try to console log store.getStore() get the following output.
Object {
"userState": Object {
"currentUser": Object {
"email": "test120#gmail.com",
"username": "test13",
},
"listings": Array [],
},
}
Now, when I dispatch my fetchUserListings() action, which should update the listings in the state the following happens.
Object {
"userState": Object {
"currentUser": Array [
Object {
"addressData": Object {
"description": "2300 Yonge Street, Toronto, ON, Canada",
"matched_substrings": Array [
Object {
"length": 3,
"offset": 0,
},
],
"place_id": "ChIJx4IytjwzK4gRwIPk2mqEJow",
"reference": "ChIJx4IytjwzK4gRwIPk2mqEJow",
"structured_formatting": Object {
"main_text": "2300 Yonge Street",
"main_text_matched_substrings": Array [
Object {
"length": 3,
"offset": 0,
},
],
"secondary_text": "Toronto, ON, Canada",
},
"terms": Array [
Object {
"offset": 0,
"value": "2300",
},
Object {
"offset": 5,
"value": "Yonge Street",
},
Object {
"offset": 19,
"value": "Toronto",
},
Object {
"offset": 28,
"value": "ON",
},
Object {
"offset": 32,
"value": "Canada",
},
],
"types": Array [
"street_address",
"geocode",
],
},
"addressDescription": "2300 Yonge Street, Toronto, ON, Canada",
"bath": "6",
"benefits": Array [
"Large Beds",
"Nearby Bustop",
"In-building gym",
],
"urls": Array [
"https://firebasestorage.googleapis.com/v0/b/studenthousingfinder-11f55.appspot.com/o/listing%2FoHr0OMukEFguYxJborrvMAJQmre2%2F0.bd7cwka5gj?alt=media&token=81b3e06a-65a9-44a7-a32d-d328014058e7",
"https://firebasestorage.googleapis.com/v0/b/studenthousingfinder-11f55.appspot.com/o/listing%2FoHr0OMukEFguYxJborrvMAJQmre2%2F0.k78etqzypk?alt=media&token=e2622547-00f4-447b-8bea-799758734f0d",
],
},
],
"listings": Array [],
},
}
Basically the API call is working and the state is updated, however somehow the data sent back is updating the currentUser in the state rather than the listings.
Here is my current reducer code:
import {USER_LISTINGS_STATE_CHANGE, USER_STATE_CHANGE} from '../constants';
const initialState = {
currentUser: null,
listings: [],
};
export const userReducer = (state = state || initialState, action) => {
switch (action.type) {
case USER_STATE_CHANGE:
return {
listings: state.listings,
currentUser: action.payload,
};
case USER_LISTINGS_STATE_CHANGE:
return {
currentUser: state.currentUser,
listings: action.payload,
};
default:
return state;
}
};
and here are the 2 functions I use to make the API request
export function fetchUser() {
return async (dispatch) => {
db.collection('users')
.doc(auth.currentUser.uid)
.get()
.then((snapshot) => {
if (snapshot.exists) {
console.log('Yo');
dispatch({type: USER_STATE_CHANGE, payload: snapshot.data()});
} else {
console.log('Does not exist');
}
});
};
}
export function fetchUserListings() {
return async (dispatch) => {
db.collection('posts')
.doc(auth.currentUser.uid)
.collection('userListings')
.orderBy('title', 'desc')
.get()
.then((snapshot) => {
let listingArr = snapshot.docs.map((doc) => {
const data = doc.data();
const id = doc.id;
return {id, ...data};
});
dispatch({
type: USER_LISTINGS_STATE_CHANGE,
payload: listingArr,
});
});
};
}
Any help would be appreciated as I'm really lost as to why this is happening!
It seems like that your users collection in database might be having wrong entry.
Fact that brought me to this conclusion is that in your fetchUserListings() function, you're actually adding an extra field to json object i.e id.
Now, the console.log output doesn't contain this id field which could only mean that fetchUserListings() is not the one being called here.
You can try putting some try catch block and console.log('fetchUser', snapshot.data()) & console.log('fetchUserListings', snapshot.docs) in respective functions to see which one is being called.

Using useListController inside ListContextProvider doesn't provide the data

I am using react-admin and I am encountering the Problem that useListController does not provide any data inside ListContextProvider even though useListContext does.
const query = useQuery({
type: 'getList',
resource: 'dashboard/summary',
payload: {
pagination: { page, perPage },
sort: { field: 'createdAt', order: 'DESC' },
filter
},
});
// more code ....
return (
<ListContextProvider
value={{
...query,
setPage,
basePath,
data: keyBy(query.data, 'id'),
ids: query.data.map(({ id }) => id),
currentSort: { field: 'id', order: 'ASC' },
selectedIds: [],
resource,
page,
perPage,
setFilters
}}
>
<SummaryList onSearch={onSearch} filter={filter} />
</ListContextProvider>
);
Inside SummaryList
const context = useListContext();
const cp = useListController(context);
console.log(context);
console.log(cp);
The controller Object
basePath: "/dashboard/summary"
currentSort: Object { field: "id", order: "ASC" }
data: Object { }
defaultTitle: "Dashboard/summaries"
displayedFilters: Object { }
error: null
exporter: function defaultExporter(data, _, __, resource)
filter: undefined
filterValues: Object { }
hasCreate: undefined
hideFilter: function hideFilter(filterName)​
ids: Array []
loaded: true
loading: false
onSelect: function select(newIds)​
onToggleItem: function toggle(id)​
onUnselectItems: function clearSelection()
page: 1
perPage: 10
resource: "dashboard/summary"
selectedIds: Array []
setFilters: function setFilters(filter, displayedFilters, debounce)​
setPage: function setPage(newPage)​
setPerPage: function setPerPage(newPerPage)​
setSort: function setSort(sort, order)​
showFilter: function showFilter(filterName, defaultValue)
total: undefined
The context Object
{
"data": {
// the data which I omitted
},
"total": 4,
"loading": false,
"loaded": true,
"basePath": "/dashboard/summary",
"ids": [
"2021-05-01",
"2021-05-02",
"2021-05-03",
"2021-05-04"
],
"currentSort": {
"field": "id",
"order": "ASC"
},
"selectedIds": [],
"resource": "dashboard/summary",
"page": 1,
"perPage": 10
}
How am I supposed to use useListController ?
useListController takes the List props (and location parameters), uses it to fetch data from the dataProvider, and passes it to the ListContext.
By calling useListContext(), you're accessing the values and callbacks built by useListController.
In your example, you're building the ListContext by hand, so you don't need useListController at all.

How to retrieve value of key from an object inside an array

I'm trying to retrieve the merchant name from within the following array:
[
{
"model": "inventory.merchant",
"pk": 1,
"fields": {
"merchant_name": "Gadgets R Us",
"joined": "2020-01-06T07:16:17.365Z"
}
},
{"model": "inventory.merchant", "pk": 2, "fields": {"merchant_name": "H&M", "joined": "2020-01-07T22:21:52Z"}},
{"model": "inventory.merchant", "pk": 3, "fields": {"merchant_name": "Next", "joined": "2020-01-07T22:22:56Z"}},
{"model": "inventory.merchant", "pk": 4, "fields": {"merchant_name": "Jill Fashion", "joined": "2020-01-07T22:26:48Z"}}
]
I'm using vuejs and have used axios to fetch the above data via an api. I put in an array called merchants[]. I'm able to get any item I want from within my html using v-for i.e.
<div v-for="merchant in merchants">
<p>{{ merchant.fields.merchant_name }}</p>
</div>
However, in my .js file, doing the following does not work:
console.log(this.merchants[0].fields.merchant_name)
I get the following error in my console:
vue.js:634 [Vue warn]: Error in render: "TypeError: Cannot read property 'fields' of undefined"
Please help
Edit:
This is my .js file. I try to log the merchants name in the console from the merchantName() computed property:
new Vue({
delimiters: ['[[', ']]'],
el: "#inventory",
data: {
gallery: true,
inCart: false,
shipping: false,
galleryView: "zoom",
ref: "",
cart: [],
isSelected: false,
selectedClass: "in_cart",
shippingCost: 2000,
inventory: [],
merchants: [],
},
methods: {
zoomGallery(){
this.galleryView = "zoom"
},
back(){
this.gallery = "thumbnail"
},
addToCart(name, merchant, price, qty, image, id){
var itemClone = {}
itemClone = {
"merchant": merchant,
"name": name,
"price": price,
"qty": qty,
"image": "/media/" + image,
"id": id,
}
this.cart.push(itemClone)
this.isSelected = true
},
removeFromCart(index){
this.cart.splice(index, 1)
},
deleteFromCart(id){
console.log(id)
// right now, any caret down button deletes any input
// I need to use components to prevent that
if (this.cart.length > 0){
index = this.cart.findIndex(x => x.id === id)
this.cart.splice(index, 1)
}
},
viewCart(){
this.gallery = false
this.shipping = false
this.inCart = true
},
viewShipping(){
this.gallery = false
this.shipping = true
this.inCart = false
}
},
computed: {
itemsInCart(){
return this.cart.length
},
subTotal(){
subTotal = 0
inCart = this.cart.length
for (i=0; i<inCart; i++) {
subTotal += Number(this.cart[i].price)
}
return subTotal
},
checkoutTotal(){
return this.subTotal + this.shippingCost
},
merchantName(){
console.log(this.merchants[0])
},
},
beforeMount() {
axios
.get("http://127.0.0.1:8000/get_products/").then(response => {
(this.inventory = response.data)
return axios.get("http://127.0.0.1:8000/get_merchants/")
})
.then(response => {
(this.merchants = response.data)
})
},
});
Edit:
Response from console.log(this.merchants) is:
[__ob__: Observer]
length: 0
__ob__: Observer
value: Array(0)
length: 0
__ob__: Observer {value: Array(0), dep: Dep, vmCount: 0}
__proto__: Array
dep: Dep
id: 16
subs: Array(0)
length: 0
__proto__: Array(0)
__proto__: Object
vmCount: 0
__proto__:
walk: ƒ walk(obj)
observeArray: ƒ observeArray(items)
constructor: ƒ Observer(value)
__proto__: Object
__proto__: Array
Why you are trying to console.log this.merchants in computed property. Check for computed property of vuejs here.
Your data is empty before data from API call even come. So that's why your this.merchants is empty.
You can get you this.merchants value by using a method and run it after your api call or watching that like this:
watch: {
merchants: function () {
console.log(this.merchants)
}
}
It will console this.merchants array everytime a change happens in it.

React: setState with spead operator seems to modify state directly

I am creating a page in React to filter attributes that are defined in my state with a isChecked like so:
this.state = {
countries: [
{ "id": 1, "name": "Japan", "isChecked": false },
{ "id": 2, "name": "Netherlands", "isChecked": true },
{ "id": 3, "name": "Russia", "isChecked": true }
//...
],
another: [
{ "id": 1, "name": "Example1", "isChecked": true },
{ "id": 2, "name": "Example2", "isChecked": true },
{ "id": 3, "name": "Example3", "isChecked": false }
//...
],
//... many more
};
I am creating a function resetFilters() to set all the isChecked to false in my state:
resetFilters() {
// in reality this array contains many more 'items'.
for (const stateItemName of ['countries', 'another']) {
// here i try to create a copy of the 'item'
const stateItem = [...this.state[stateItemName]];
// here i set all the "isChecked" to false.
stateItem.map( (subItem) => {
subItem.isChecked = false;
});
this.setState({ stateItemName: stateItem });
}
this.handleApiCall();
}
My problem is: it seems I am directly modifying state, something that is wrong, according to the docs. Even though my function seems to work, when I remove the line this.setState({ stateItemName: stateItem }); it will also seem to work and when I console log stateItem and this.state[stateItemName] they are always the same, even though I am using the spread operator which should create a copy. My question: how is this possible / what am I doing wrong?
That is because the spread syntax does only shallow copying. If you want to carry out deep copying, you should also be spreading the inner objects within each array.
for (const stateItemName of ['countries', 'another']) {
const stateItem = [...this.state[stateItemName]];
const items = stateItem.map( (subItem) => ({
...subItem,
isChecked: false,
}));
this.setState({ [stateItemName]: items });
}
I think your code could be reduced more so, for example an approach could be:
function resetFilters() {
const targetItems = ['countries', 'another'];
const resetState = targetItems.reduce((acc, item) => ({
...acc,
[item]: this.state[item].map(itemArray => ({
...itemArray,
isChecked: false
}))
}), {})
this.setState(state => ({ ...state, ...resetState }), this.handleApiCall);
}
The benefit here is that the api call is done after state is updated. While updating current state correctly.
Let me know how it works out 👌🏻
-Neil

Resources