Is there an alternative to lodash .chain function? - javascript

How do I achieve this without lodash chain function?
const results = chain(sortedItems || allItems)
.filter((x) => this.filterItemsBySearchParam<T>(x, search))
.filter((x) => this.filterItemsByFacets<T>(x, facets))
.groupBy((x) => (groupBy ? [groupBy.split(',').map((path) => get(x, path))] : ''))
.map((filteredItems: any, key) => {
if (!isNaN(Number(limit))) {
filteredItems = [...filteredItems.slice(0, limit)];
}
return this.addData<T>(key, filteredItems.length, filteredItems);
})
.value();
I have tried using lodash flow, and some other ES6 functions, but none of them worked as expected. it could be that I'm not applying them correctly?
I have tried this:
const result = sortedItems || allItems
.filter((x) => this.filterItemsBySearchParam<T>(x, search))
.filter((x) => this.filterItemsByFacets<T>(x, facets))
.groupBy((x) => (groupBy ? [groupBy.split(',').map((path) => get(x, path))] : ''))
.map((filteredItems: any, key) => {
if (!isNaN(Number(limit))) {
filteredItems = [...filteredItems.slice(0, limit)];
}
return this.addData<T>(key, filteredItems.length, filteredItems);
});
But the groupBy keeps throwing an error: groupBy is not a type of T.
I tried flow from here: Better and more performant than lodash chain, but like I said, I could not get it to work. Is there a way to achieve this? By the way, the .filter, .map, and .groupBy are all built-in ts functions.

Try this:
const filteredItems = (sortedItems || allItems || []).filter(x => this.filterItemsBySearchParam<T>(x, search) && this.filterItemsByFacets<T>(x, facets));
const results = Object.entries(groupBy ? filteredItems.reduce((map, x) => {
const key = JSON.stringify((groupBy.match(/[^,]+/g) || []).map(path => get(x, path)));
(map[key] || (map[key] = [])).push(x);
return map;
}, {}) : {'': filteredItems}).map(([key, filteredItems]) => {
if (+limit > 0) {
filteredItems = filteredItems.slice(0, limit);
}
return this.addData<T>(key, filteredItems.length, filteredItems);
});
ask if you have any questions :)

Related

Improve useEffect mutating array objects and get difference beetween

I need help to see if I can improve this code that I have, first I describe the final result that I hope and maybe this way you can find how to improve a little this useEffect:
I need to receive that data object, compare it with one previously stored in localstorage, and compare them to see if there is any new record in that object, if it finds new data, extract them, add the isNew property only to those key that found new and create a new array with the data I received and newArray that includes the modified data.
const localStorageKey = 'laboratoryData'
const oldData = localStorage.getItem(localStorageKey)
useEffect(() => {
if (data) {
if ([null, undefined].includes(oldData)) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data))
} else {
const arrayIsEqual = (data, oldData) => {
data.data === oldData ||
(data.data.length === oldData.length &&
data.data.every(
(f, i) => f.id === oldData[i].id && f.name === oldData[i].name,
))
}
if (arrayIsEqual) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data))
} else {
const diff = data.data
.filter((x) => !oldData.includes(x))
.concat(oldData.filter((x) => !data.data.includes(x)))
const newArray = diff.map((obj) => ({...obj, isNew: 'true'}))
const finalArray = {...data, ...newArray}
localStorage.setItem(localStorageKey, JSON.stringify(finalArray))
}
}
}
}, [data])
Maybe i`m duplicating some code or missing a custom hook with a better performance
Thanks!
without seeing your data and based on your code snippet, hopefully your data.data and oldData are always be arrays of objects that have always have key id and name.
const localStorageKey = 'laboratoryData';
const oldData = localStorage.getItem(localStorageKey);
useEffect(() => {
if (!data) return;
if (!oldData) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data));
return;
}
if (data.data.length === oldData.length && data.data.every((d, i) => d.id === oldData[i].id && d.name === oldData[i].name)) return;
const newData = data.data.filter(d => oldData.findIndex(od => od.id === d.id && od.name === d.name) === -1).map(d => d["isNew"] = true);
const finalArray = oldData.map(od => od["isNew"] = false).concat(newData);
localStorage.setItem(localStorageKey, JSON.stringify(finalArray));
}, [data])

Adding deep nested object properties to Window in JS

I would like to clean up this block of code. Is there a way of setting deep object properties without using Lodash, Ramda or some horrible method that splits the object property string and loops through it?
export const initialiseBlackbox = (value = '') => {
if (window === undefined) {
window = { IGLOO }
}
if (window.IGLOO === undefined) {
window.IGLOO = {}
}
if (window.IGLOO.getBlackbox === undefined) {
window.IGLOO.getBlackbox = () => ({ blackbox: value })
}
}
Sure, but it's not pretty:
export const initialiseBlackbox = (value = '') =>
Object.assign(window.IGLOO || (window.IGLOO = {}),
{ getBlackbox: () => ({ backbox: value }) });
For a generic reusable function
const deepSet = (t, p, ...v) => v.length > 1 ? (t[p] = t[p] || {}) && deepSet(t[p], ...v) : t[p] = v[0]
To use
deepSet(window, "IGLOO", "getBlackbox", () => ({ blackbox: 'some value' }))

How do I write lodash orderBy function in plain typescript?

I want to remove lodash from my project, one of the functions I'm using is orderBy. I followed this post in trying to replace the function: How would I write the Lodash function orderBy in VanillaJS?, but I'm getting the error message: .reverse is not a function. Here is my implementation:
private sortBy(key, cb) {
if (!cb) cb = () => 0;
return (a, b) => ((a[key] > b[key]) ? 1 :
((b[key] > a[key]) ? -1 : cb(a, b)));
}
private sortByDesc(key, cb) {
if (!cb) cb = () => 0;
return (b, a) => ((a[key] > b[key]) ? 1 :
((b[key] > a[key]) ? -1 : cb(b, a)));
}
private _orderBy(keys: any[], orders: any[]) {
let cb :any;
keys.reverse();
orders.reverse();
for (const [i, key] of keys.entries()) {
const order = orders[i];
if (order === 'asc') { cb = this.sortBy(key, cb); } else if (order === 'desc') { cb = this.sortByDesc(key, cb); } else throw new Error(`Unsupported order "${order}"`);
}
return cb;
}
And I'm trying to use it like so:
public async getResults<T>(
sport: SportType,
linkParam: string,
linkValue: string,
groupBy?: string,
orderBy?: string,
sortBy?: string,
facets?: string,
limit?: number,
search?: string,
): Promise<Data | undefined> {
const allItems = await this.getResultsbyParamId<T>(
sport,
linkParam,
linkValue,
);
const sortedItems =
!!orderBy &&
allItems.concat().sort(this._orderBy(orderBy.split(','), !!sortBy && (sortBy.split(',') as ('asc' | 'desc')[])));
// _orderBy(
// allItems,
// orderBy.split(','),
// !!sortBy && (sortBy.split(',') as ('asc' | 'desc')[]),
// );
const totalItems = allItems?.length || 0;
const results = chain(sortedItems || allItems)
.filter((x) => this.filterItemsBySearchParam<T>(x, search))
.filter((x) => this.filterItemsByFacets<T>(x, facets))
.groupBy((x) => (groupBy ? [groupBy.split(',').map((path) => get(x, path))] : ''))
.map((filteredItems: any, key) => {
if (!isNaN(Number(limit))) {
filteredItems = [...filteredItems.slice(0, limit)];
}
return this.addData<T>(key, filteredItems.length, filteredItems);
})
.value();
return this.addMeta(
results,
totalItems,
limit !== 0 ? this.getFacetOptions<T>(allItems) : [],
);
}
But orders.reverse(); is throwing that error. (.reverse is not a function). What am I doing wrongly? or is there a better way to achieve this?

ES6 filter in a filter is this possible?

I have an Array that looks like this:
I need the id where slug === 'slug'
let activeFaq = this.faq.filter(obj => obj.items.filter(item => item.slug === 'slug'));
console.log(activeFaq.id);
This gives me an undefined. I should get 2729
There are multiple ways you could go about this. If you can use .flatMap, then one solution would be to flatten all items into a single array and then use .find:
const item = this.faq.flatMap(obj => obj.items).find(item => item.slug === 'slug')
However, flattening seems a bit unnecessary. An approach that avoid unnecessary computations would be a helper function with a boring loop:
function findItem(faqs, callback) {
for (const faq of faqs) {
const item = faq.find(callback);
if (item) {
return item;
}
}
}
const activeFaq = findItem(this.faq, item => item.slug === 'slug');
var activeFaq = this.faq.filter(obj => obj.items.some(item => item.slug === 'slug'))[0];
console.log(activeFaq.items[0].id); //2729
let activeFaq = this.faq.reduce( ( obj ) => {
const foundItem = obj.items.find( ( item ) => {
return item.slug === 'slug'
} )
if( foundItem ) {
acc = foundItem
}
return acc
}, null )
console.log(activeFaq.id);

How to join two half in RxJs

I have a stream like this
---ab---ab---a---ba---bab---ab---ab---ab--->
And I want this.
---ab---ab------ab----ab-ab-ab---ab---ab--->
The point is, that I have data with beginning and end (JSON) and sometimes the data is cut in the half in the stream, and I want to join them again. How can I do that?
Looks like a job for the scan operator
// substitute appropriate real-world logic
const isProperlyFormed = (x) => x === 'ab'
const isIncomplete = (x) => x[0] === 'a' && x.length === 1
const startsWithEnding = (x) => x[0] === 'b'
const getCorrected = (buffer, x) => buffer.prev + x[0]
const getTail = (buffer, x) => x.slice(1)
const initialBuffer = {
emit: [],
prev: null
}
const result = source
.scan((buffer, x) => {
if (isProperlyFormed(x)) {
buffer = {emit: [x], prev:null}
}
if (isIncomplete(x)) {
buffer = {emit: [], prev:x}
}
if (startsWithEnding(x)) {
const corrected = getCorrected(buffer, x)
const tail = getTail(buffer, x)
if (isProperlyFormed(tail)) {
buffer = {emit: [corrected, tail], prev: null}
} else {
buffer = {emit: [corrected], prev: tail}
}
}
return buffer
}, initialBuffer)
.flatMap(x => x.emit)
Working CodePen
Edit
Looking at the test input stream, I think a case is missing, which will break the above.
I changed the test from
---ab---ab---a---ba---bab---ab---ab---ab--->
to
---ab---ab---a---ba---bab---aba---b---ab--->
and also slimmed down the algorithm
const getNextBuffer = (x) => {
const items = x.split(/(ab)/g).filter(y => y) // get valid items plus tail
return {
emit: items.filter(x => x === 'ab'), // emit valid items
save: items.filter(x => x !== 'ab')[0] // save tail
}
}
const initialBuffer = {
emit: [],
save: null
}
const result = source
.scan((buffer, item) => {
const bufferAndItem = (buffer.save ? buffer.save : '') + item
return getNextBuffer(bufferAndItem)
}, initialBuffer)
.flatMap(x => x.emit)
Working example CodePen
First split the stream into full responses and partial. Then check if response is full. Full responses are good as such. Partial responses need to be synchronized, so we split their stream into first and second halves and just zip those streams together.
The strange looking Rx.Observable.of(g.partition(x => x[0] === 'a')) is because partition operator returns pair of observables, which cannot be chained.
const testStream = Rx.Observable.of('a1', 'a2', '_ab', 'b1', 'a3', 'b2', '_ab', 'a4', 'b3', '_ab', 'b4', 'a5', 'b5', '_ab')
testStream
.groupBy(x => (x[0] === '_' && 'full') || 'partial')
.mergeMap(g =>
Rx.Observable.if(
() => g.key == 'full',
g,
Rx.Observable.of(g.partition(x => x[0] === 'a'))
.mergeMap(([as, bs]) => Rx.Observable.zip(as, bs))
)
)
.do(x => console.log(x))
.subscribe()
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.3/Rx.min.js"></script>
This is how I solved:
import Rx from 'rxjs/Rx';
import {last} from 'lodash';
const data$ = Rx.Observable.of('ab','ab','a','ba','bab','aba','b','ab');
const line$ = data$.flatMap(data => {
const lines = data.match(/[^b]+b?|b/g); // https://stackoverflow.com/a/36465144/598280 https://stackoverflow.com/a/25221523/598280
return Rx.Observable.from(lines);
});
const isComplete$ = line$.scan((acc, value) => {
const isLineEndingLast = last(acc.value) === 'b';
const id = isLineEndingLast ? acc.id + 1 : acc.id;
const complete = last(value) === 'b';
return {value, id, complete};
}, {value: 'b', id: 0, complete: true});
const grouped$ = isComplete$
.groupBy(data => data.id, data => data, group => group.first(data => data.complete))
.flatMap(group => group.reduce((acc, data) => acc + data.value, ''));
grouped$.subscribe(console.log);

Categories

Resources