Firestore: onWrite Convert Keywords Array String to Lower Case Items - javascript

Scenario
I want to able to change array of keywords to lowercase on either update or create operation, however on delete I want to exit function execution early.
I have done the below thus far
import * as functions from "firebase-functions";
export const onCourseCreate = functions.firestore.document("courses/{id}")
.onWrite((snap, context) => {
return changeToLowerCase(snap);
});
function changeToLowerCase(
snap: functions.Change<FirebaseFirestore.DocumentSnapshot>
) {
if (!snap.before.exists) return null;
const original = snap.before.data();
if (original == undefined) return;
const _keywords: string[] = original.keywords;
const keywords: string[] = [];
_keywords.forEach((item: string) => {
const lowercase = item.toLowerCase();
keywords.push(lowercase);
});
return snap.after.ref.set(
{
keywords
},
{ merge: true }
);
}
Also
I have tired snap.after.exists.
The Problem
Once I delete an item in the keywords array I go through infinite loop
Any suggestions I would be very much appreciative.
Thanks.

Solution
The problem to the above issue is that I was trying to delete an item from keywords array field. When deleting an item I am referring to the original instead I should have referred to the modified snap.after.data().
It's a non general problem and it is peculiar to my specific use case. For the interested here is the final solution:
import * as functions from "firebase-functions";
export const onCourseCreate = functions.firestore.document("courses/{id}")
.onWrite((snap, context) => {
return changeToLowerCase(snap);
});
function changeToLowerCase(
snap: functions.Change<FirebaseFirestore.DocumentSnapshot>
) {
if (!snap.after.exists) return null;
const original = snap.before.data();
const modified = snap.after.data();
if (modified == undefined) return null;
if (original != undefined && modified.keywords.length < original.keywords.length)
return null;
const _keywords: string[] = modified.keywords;
const keywords: string[] = [];
_keywords.forEach((item: string) => {
const lowercase = item.toLowerCase();
keywords.push(lowercase);
});
return snap.after.ref.set(
{
keywords
},
{ merge: true }
);
}

Related

Vue 3 ref array doesnt update values

Description
I'm new to Vue 3 and the composition API.
When i try to delete values in an array declared with ref, values do not delete.
Code preview
Here is my code
<script setup lang="ts">
import { IPartnerCategory, IPartners } from '~~/shared/types'
const selectedPartnershipCategories = ref([])
const props = withDefaults(
defineProps<{
partnershipCategories?: IPartnerCategory[]
partnerships?: IPartners[]
freelancer?: boolean
}>(),
{
partnershipCategories: () => [],
partnerships: () => [],
freelancer: false,
}
)
const emit =
defineEmits<{
(e: 'update:value', partnership: IPartnerCategory): void
(e: 'update:selected', select: boolean): void
}>()
const updateSelectedPartnership = (partnershipId: string, categorySelected: boolean) => {
if (categorySelected && !selectedPartnershipCategories.value.includes(partnershipId)) {
return selectedPartnershipCategories.value.push(partnershipId)
}
if (!categorySelected && selectedPartnershipCategories.value.includes(partnershipId)) {
const clearedArray = selectedPartnershipCategories.value.filter((i) => {
return i !== partnershipId
})
console.log(clearedArray)
}
}
const select = (event) => {
updateSelectedPartnership(event.fieldId, event.isSelected)
}
</script>
My array is declared as selectedPartnershipCategories
I've a function named updateSelectedPartnesh, called everytime when i update a value in the selectedPartnership array
When i log clearedArray values are only pushed but not deleted.
Thanks in advance for your help :)
This is because filter creates a shallow copy and does not modify the original array.
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
console.log(words);
If you want to modify selectedPartnership, you should use splice or another method, or simply selectedPartnership = selectedPartnership.filter(...).

how to indicate loaging/searching on Office FabricUI TagPicker for large data sets?

I am using the TagPicker to get data dynamically and present a set of the results that matches with the term. The issue is that looking into the docs there is not clear indication how to determine that the component data is loading or searching. The interface that had those was dropped (ISuggestionsProps) and the loadingText prop does not seem to work for me or I am probably using it wrong.
here is how I was able to load data from a list into the tagpicker:
const filterSuggestedTags = async (filterText: string, tagList: ITag[]) => {
//* possibly here to call an api if needed?
if (filterText) {
const url = 'url'
const resp = await fetch(url,{method:'GET',headers:{Accept:'application/json; odata=verbose'}})
return (await resp.json()).d.results.map(item => ({ key: item, name: item.Title }));
} else return []
};
codepen:
https://codepen.io/deleite/pen/MWjBMjY?editors=1111
This obviously has a lot of problems, first and the worst every keystroke is a promise fired. So, question is how to call an api using the search term and result the suggestions?
Thank you all.
I am ussing Office ui Fabric react v5 ("office-ui-fabric-react": "^5.135.5").
I am using TagPicker to laod external API data (long resolve time, large data set).
Loading suggestions is delayed for 700ms (after key pressed).
Loading suggestions is fired after 3 chars are typed.
During loading there is loading circle visible. I am loading suggestion in pages for 20 suggestions, if there is more items to be loaded on the bottom there is Loading more anchor which loads another page and add new suggestions to already loaded. I had to extend IBasePickerSuggestionsProps interface for moreSuggestionsAvailable?: boolean; in BasePicker.types.d.ts based on this issue: https://github.com/microsoft/fluentui/issues/6582
Doc: https://developer.microsoft.com/en-us/fluentui#/components/pickers
Codepen: https://codepen.io/matej4386/pen/ZEpqwQv
Here is my code:
const {disabled} = this.props;
const {
selectedItems,
errorMessage
} = this.state;
<TagPicker
onResolveSuggestions={this.onFilterChanged}
getTextFromItem={this.getTextFromItem}
resolveDelay={700}
pickerSuggestionsProps={{
suggestionsHeaderText: strings.suggestionsHeaderText,
noResultsFoundText: strings.noresultsFoundText,
searchForMoreText: strings.moreSuggestions,
moreSuggestionsAvailable: this.state.loadmore
}}
onGetMoreResults={this.onGetMoreResults}
onRenderSuggestionsItem={this.onRenderSuggestionsItem}
selectedItems={selectedItems}
onChange={this.onItemChanged}
itemLimit={1}
disabled={disabled}
inputProps={{
placeholder: strings.TextFormFieldPlaceholder
}}
/>
private onFilterChanged = async (filterText: string, tagList:IPickerItem[]) => {
if (filterText.length >= 3) {
let resolvedSugestions: IPickerItem[] = await this.loadListItems(filterText);
const {
selectedItems
} = this.state;
// Filter out the already retrieved items, so that they cannot be selected again
if (selectedItems && selectedItems.length > 0) {
let filteredSuggestions = [];
for (const suggestion of resolvedSugestions) {
const exists = selectedItems.filter(sItem => sItem.key === suggestion.key);
if (!exists || exists.length === 0) {
filteredSuggestions.push(suggestion);
}
}
resolvedSugestions = filteredSuggestions;
}
if (resolvedSugestions) {
this.setState({
errorMessage: "",
showError: false,
suggestions: resolvedSugestions,
loadmore: true,
loadMorePageNumber: 1
});
return resolvedSugestions;
} else {
return [];
}
} else {
return null
}
}
private onGetMoreResults = async (filterText: string, selectedItems?: any[]): Promise<IPickerItem[]> => {
let arrayItems: IPickerItem[] = [];
try {
let listItems: IOrganization[] = await this.GetOrganizations(this.Identity[0].id, filterText, 1);
...
private loadListItems = async (filterText: string): Promise<IPickerItem[]> => {
let { webUrl, filter, substringSearch } = this.props;
let arrayItems: IPickerItem[] = [];
try {
...
Ok, you can mitigate this problem with the prop 'resolveDelay' but still I did not find any standard way to handle items from an api this is the closest I came up.
Any samples or ideas would be appreciated.

How to sort an array of complex objects?

I have this method for sorting an array of objects, but when I have a complex structure, for example:
const data = [ { title: 'Book title', author: 'John', info: { language: english, pages: 500, price: '$50' }} ]
I can't sort the second level of the object 'info: {language: english, pages: 500, price:' $ 50 '}'
My Code:
import { useMemo, useState } from 'react';
interface SortConfigProps {
key: string;
direction: string;
}
export const useSortableData = <T>(items: T[]) => {
const [sortConfig, setSortConfig] = useState<SortConfigProps>(
{} as SortConfigProps,
);
const sortedItems = useMemo(() => {
const sortableItems = [...items];
if (sortConfig) {
sortableItems.sort((a: any, b: any) => {
if (a[sortConfig.key] < b[sortConfig.key]) {
return sortConfig.direction === 'ascending' ? -1 : 1;
}
if (a[sortConfig.key] > b[sortConfig.key]) {
return sortConfig.direction === 'ascending' ? 1 : -1;
}
return 0;
});
}
return sortableItems;
}, [items, sortConfig]);
const requestSort = (key: string) => {
let direction = 'ascending';
if (
sortConfig &&
sortConfig.key === key &&
sortConfig.direction === 'ascending'
) {
direction = 'descending';
}
setSortConfig({ key, direction });
};
return { items: sortedItems, requestSort, sortConfig };
};
There are multiple ways to implement this based on your overall system so for my suggestion, I am going to assume the following:
You want to keep the data as is for performance or some other reasons (i.e. flattening the object through some kind of "normalizer" is not an option).
The key cannot be an object, but has to be a string
Either you or the user can set the key.
There exists a character or a set of characters that can be used in a key string as a delimiter to construct a tree of keys (e.g. a dot in 'info.price', or an arrow in 'info->price'). An important property of the delimiter is that it is not valid to use it in a flat key (i.e. in the last example something like data = [{ 'info->price': '$50' }] is not allowed)
Ok now you just need to implement an accessor to use the complex keys on your object, something similar to Lodash.get. A simple implementation would be something like:
const DELIMITER = '->';
function get(obj, key) {
if (!obj) {
// in case of null or undefined
return obj;
}
if (!key) {
// empty string or something like that
return obj;
}
if (key.includes(DELIMITER)) {
const keyComponents = key.split(DELIMITER);
const firstKeyComponent = keyComponents.shift();
const newKey = keyComponents.join(DELIMITER);
return get(obj[firstKeyComponent], newKey)
}
return obj[key];
}
Emphasis on the simple here, because recalculating keyComponents every time is not ideal. You also might want add extra if cases for how to handle strings or arrays, those could cause problems if a key component is a number.
EDIT: Also maybe use Object.prototype.hasOwnProperty to check if a key is identical to a built in Object function, or better yet check if obj is a function and decide how to handle that scenario.
 
After you have this you can just replace that compare segment of your code with this:
sortableItems.sort((a: any, b: any) => {
const aValue = get(a, sortConfig.key);
const bValue = get(b, sortConfig.key);
if (aValue < bValue) {
return sortConfig.direction === 'ascending' ? -1 : 1;
}
if (aValue] > bValue) {
return sortConfig.direction === 'ascending' ? 1 : -1;
}
return 0;
});
And you're good to go for most cases. I don't know how "wild" your data can get so make sure to test a bunch of scenarios.
The problem you are running into is deep cloning of an object. the spread operator only goes 1 level deep and you are trying to go two. You can use libraries from lodash or other deep cloning. I use the JSON trick.
const [data, setData] = useState(initData);
function newArray() {
return JSON.parse(JSON.stringify(data));
}
Example shows sorting with two levels: https://codesandbox.io/s/serverless-sea-jt220?file=/src/App.js

Duplicate code Javascript. how to modularize it

I am facing a duplicate issue in Sonar and i am not able to figure out how to correct it.
Here is the sample code. Please not formFields passed is immutable state maintained in my project. So if i do formFields.getIn(['home', 'value']), i am trying to get the value of particular field home in this case. and i compare all values with 'true'. and once i compare i push its respective string in lifeEvents. These lines (3,4 and 5,6) show tat i am duplicating the comparison and pushing of data to the array.
1. export const getLifeEvents = formFields => {
2. const lifeEvents = [];
3. if(formFields.getIn(['home', 'value']) === 'true')
4. lifeEvents.push("Buy home");
5. if(formFields.getIn(['married', 'value']) === 'true')
6. lifeEvents.push("Getting married");
7. return lifeEvents;
8. }
To avoid this duplication, i tried the following
export const getLifeEvents = formFields => {
const lifeEvents = [];
const Info = {
title: ['home', 'married'],
text: ['Buy home', 'Getting married']
}
const data = Info.title.map((e, i) => {
return { title: e, text: Info.text[i]
}
const result = data && data.map(item => {
if(formFields.getIn([item.title, 'value']) === 'true')
lifeEvents.push(item.text);
return lifeEvents;
});
}
When i do this, i always get undefined. can someone suggest please
Create an object with the keys and text. Loop over it with reduce
const myEvents = {
home: 'Buy home',
married: 'Getting married'
};
export const getLifeEvents = formFields => {
return Object.entries(myEvents).reduce((lifeEvents, [key, text]) => {
if (formFields.getIn([key, 'value']) === 'true') {
lifeEvents.push(text);
}
return lifeEvents;
}, []);
}

Updating a two level nested object using immutability-helper in react and typescript

I have an array of object called TourStop, which is two level nested. The types are as below.
TourStops = TourStop[]
TourStop = {
suggestions?: StoreSuggestion[]
id: Uuid
}
StoreSuggestion= {
id: Uuid
spaceSuggestions: SpaceSuggestion[]
}
SpaceSuggestion = {
id: Uuid
title: string
}
My goal is to remove a particular StoreSuggestion from a particular TourStop
I have written the following code(uses immutability-helper and hooks)
const [tourStopsArray, setTourStopsArray] = useState(tourStops)
// function to remove the store suggestion
const removeSuggestionFromTourStop = (index: number, tourStopId: Uuid) => {
// find the particular tourStop
const targetTourStop = tourStopsArray.find(arr => arr.id === tourStopId)
// Update the storeSuggestion in that tourstop
const filteredStoreSuggestion = targetTourStop?.suggestions?.filter(sugg => sugg.id !== index)
if (targetTourStop) {
// create a new TourStop with the updated storeSuggestion
const updatedTargetTourStop: TourStopType = {
...targetTourStop,
suggestions: filteredStoreSuggestion,
}
const targetIndex = tourStopsArray.findIndex(
tourStop => tourStop.id == updatedTargetTourStop.id,
)
// Find it by index and remove it
setTourStopsArray(
update(tourStopsArray, {
$splice: [[targetIndex, 1]],
}),
)
// add the new TourStop
setTourStopsArray(
update(tourStopsArray, {
$push: [updatedTargetTourStop],
}),
)
}
}
The push action works correctly. However the splice action doesn't work for some reason. What am I doing wrong?

Categories

Resources