lost scope in callback, how to use bind effectively - callback

in my case localesCollection within compareValue is having an old (not updated) value. State is not real-time. I assumed bind was here to save my day.
// Redux
let localesCollectionValues = useSelector((state: IStore) => state.localesStoreSlice.localesCollectionValues);
const compareValue = (fieldValue: any): void => {
console.log('compareValue', fieldValue, localesCollectionValues);
}
const attachListeners = () => {
console.log('attachListeners');
Object.keys(sdk.entry.fields).forEach((field: string) => {
const fieldRef: any = sdk.entry.fields[field];
fieldRef.locales.forEach((localeKey: string) => {
fieldRef.getForLocale(localeKey).onValueChanged(compareValue.bind(this));
});
});
};
edit:
is was coming from having the arrow function directly in the callback with the same issue as described above
edit2:
the value of 'localesCollectionValues' is the value on the moment that the code get initialised and the handler gets set. After that its not updated and it just keeps hold of n old reference. Typical memory pointer / scope issue, in need of a direction to solve this.
fieldRef.getForLocale(localeKey).onValueChanged(() => {
console.log(localesCollectionValues) // <- not real time,
// contains old value
});

In hindsight I just had to follow React Hooks directions for State Management, makes perfect sense:
const [changedLocale, setChangedLocale] = useState('');
useEffect(() => {
console.log('changedLocale', changedLocale, localesCollection);
// accurate, real-time
}, [changedLocale, localesCollection]);
const attachListeners = () => {
Object.keys(sdk.entry.fields).forEach((field: string) => {
sdk.entry.fields[field].locales.forEach((localeKey: string) => {
const fieldRef: any = sdk.entry.fields[field];
fieldRef.getForLocale(localeKey).onValueChanged(() => {
setChangedLocale(localeKey);
});
});
});
};

Related

Failed to add new elements when set initialState as an empty object

I try to use redux toolkit and I have this as menu-slice.js
I try to use property accessors to add a new property to fileItems, its initial value is an empty object.
import { createSlice } from "#reduxjs/toolkit";
const menuSlice = createSlice({
name: "ui",
initialState: {
fileItems: {},
},
reducers: {
setFileDate: (state, action) => {
state.FileDate = action.payload;
},
replaceFileItems: (state, action) => {
const filesList = action.payload.map((fileName) =>
fileName.slice(fileName.indexOf("/") + 1)
);
state.fileItems[state.FileDate] = filesList;
console.log(`filesList: ${filesList}`);
console.log(`state.fileItems: ${JSON.stringify(state.fileItems)}`);
console.log(`state.FileDate: ${state.FileDate}`);
state.fileContents = null;
},
I call dispatch with the api return value ( dispatch(menuActions.replaceFileItems(fileResponse.data));)
in menu-action.js:
the return value is an array of strings.
export const fetchFiles = (fileDate) => {
return async (dispatch) => {
const fetchFilesList = async () => {
const response = await fetch(
"some url" +
new URLSearchParams({
env: "https://env.com",
date: fileDate,
})
);
if (!response.ok) {
throw new Error("Fail to fetch files list!");
}
const data = await response.json();
return data;
};
try {
const fileResponse = await fetchFilesList();
dispatch(menuActions.setFileDate(FileDate));
dispatch(menuActions.replaceFileItems(fileResponse.data));
} catch (error) {
dispatch(
menuActions.showNotification({....
})
);
}
};
};
But it never prints console logs and didn't display where went wrong in the console or in the chrome redux extension.
I want to add data into state.fileItems on each click that triggers fetchFiles() when it returns a new array:
from state.fileItems = {}
check if state.fileItems already has the date as key,
if not already has the date as key,
change to ex: state.fileItems = {"2022-01-01": Array(2)}
and so on..
ex: state.fileItems = { "2022-01-01": Array(2), "2022-01-02": Array(2) }
I also tried to set state.fileItems as an empty array, and use push, but it didn't work either, nothing printed out, state.fileItems value was always undefined.
Can anyone please tell me why this didn't work?
Thanks for your time to read my question.

service-worker registered and precaching works fine, but fetch event is never firing

I'm quite new to PWA, service workers and workbox. I don't understand why and how the fetch event inside my service worker is supposed to be triggered?
I see the log of workbox which is precaching files I've provided as an array:
How would I serve from cache now and why does my fetch event handler won't be fired at all?
My service-worker file:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.2.0/workbox-sw.js')
workbox.setConfig({
debug: true,
})
// To avoid async issues, we load strategies before we call it in the event listener
workbox.loadModule('workbox-core')
workbox.loadModule('workbox-routing')
workbox.loadModule('workbox-cacheable-response')
workbox.loadModule('workbox-strategies')
workbox.loadModule('workbox-expiration')
workbox.loadModule('workbox-precaching')
const { precacheAndRoute } = workbox.precaching
const wbManifest = self.__WB_MANIFEST
console.log(wbManifest)
precacheAndRoute(wbManifest);
const cacheNames = workbox.core.cacheNames
const { registerRoute, setCatchHandler, setDefaultHandler } = workbox.routing
const { CacheableResponsePlugin } = workbox.cacheableResponse
const {
NetworkFirst,
StaleWhileRevalidate,
NetworkOnly,
} = workbox.strategies
const { ExpirationPlugin } = workbox.expiration
const cacheName = cacheNames.runtime
const contentToCache = [
'/',
]
for (const entry of wbManifest) {
contentToCache.push(entry.url)
}
self.addEventListener('activate', e => {
e.waitUntil(self.clients.claim()) // Become available to all pages
})
self.addEventListener('install', e => {
e.waitUntil((async () => {
const cache = await caches.open(cacheName)
console.log('[Service Worker] Caching content', contentToCache)
await cache.addAll(contentToCache)
self.skipWaiting()
})())
})
self.addEventListener('push', e => {
console.log(e.data.text());
});
self.addEventListener('fetch', e => {
const { request } = e;
console.log(request)
e.respondWith(caches.match(request).then(cachedResponse => {
// This promise explicitly resolves with "undefined" when there are no matches, all other values are correct
if (cachedResponse !== undefined) {
return cachedResponse
} else {
return fetch(request).then(response => {
// Since we can use the response only once, put the clone into the cache and serve the original response
const responseClone = response.clone()
caches.open('CACHE_KEY_WHATEVER').then(cache => {
cache.put(request, responseClone)
})
return response
}).catch(() => {
// Retry logic
});
}
}))
})
precacheAndRoute(wbManifest); will both precache (during install, add the entries to the cache) and route (respond to fetch events with a cached response) for all entries that are present in wbManifest.
I see that your current code attempts to do both the precaching (in your own install handler) and routing (in your own fetch handler) for all of those URLs. If you would prefer to handle that yourself, that's fine, but in that case you shouldn't be calling precacheAndRoute() at all. You're attempting to do the same thing as that method, but the event listeners for Workbox's precacheAndRoute() ends up executing first and take precedence.

AST - Find if object keys have usage

Very very inexperienced with AST parsing, been at it for a day making all rite progress got it working on a single file, but not through imports.
The idea is that i want to tag an object with a comment to flag it for extremely aggressive tree shaking.
In
// (object-method.js)
export const ObjectMethods = /*#__PURE__*/ {
foo: () => "do foo",
bar: () => "do bar"
}
// index.js
import { ObjectMethods } from "./object-methods";
export const runActions = () => {
ObjectMethods .bar();
};
out (foo is deleted as object key because its never used)
// (object-method.js)
export const ObjectMethods = /*#__PURE__*/ {
bar: () => "do bar"
}
// index.js
import { ObjectMethods } from "./object-methods";
export const runActions = () => {
ObjectMethods .bar();
};
Its basically a glorified "usage" detector, just has to be real basic only support objects declared at top-level scope with the comment.
This is what iv'e got up to but then this only works on usage within a single file....
const PURE_IDENTIFIER = "#__PURE__";
export default function() {
return {
visitor: {
ObjectExpression(program) {
if(!program.node.leadingComments) return;
if(program.node.leadingComments.some((comment) => comment.value === PURE_IDENTIFIER)) {
let objectKeys = program.node.properties.map((prop) => prop.key.loc.identifierName);
let found = [];
program.parentPath.parentPath.parentPath.parentPath.traverse({
Identifier(p) {
if(objectKeys.includes(p.node.loc.identifierName)) {
found.push(p.node.loc.identifierName);
}
}
});
let duplicates = found.filter((item, index) => found.indexOf(item) === index && found.lastIndexOf(item) !== index);
program.node.properties = program.node.properties.filter((prop) => {
/* REMOVES DUPLICATES */
return duplicates.includes(prop.key.loc.identifierName)
});
}
},
},
};
}
The question is how can i support multifile usage detection and removal - Thanks

How to properly use jasmine-marbles to test multiple actions in ofType

I have an Effect that is called each time it recives an action of more than one "kind"
myEffect.effect.ts
someEffect$ = createEffect(() =>
this.actions$.pipe(
ofType(fromActions.actionOne, fromActions.actionTwo),
exhaustMap(() => {
return this.myService.getSomeDataViaHTTP().pipe(
map((data) =>
fromActions.successAction({ payload: data})
),
catchError((err) =>
ObservableOf(fromActions.failAction({ payload: err }))
)
);
})
)
);
in my test I tried to "simulate the two different actions but I always end up with an error, while if I try with one single action it works perfectly
The Before Each part
describe('MyEffect', () => {
let actions$: Observable<Action>;
let effects: MyEffect;
let userServiceSpy: jasmine.SpyObj<MyService>;
const data = {
// Some data structure
};
beforeEach(() => {
const spy = jasmine.createSpyObj('MyService', [
'getSomeDataViaHTTP',
]);
TestBed.configureTestingModule({
providers: [
MyEffect,
provideMockActions(() => actions$),
{
provide: MyService,
useValue: spy,
},
],
});
effects = TestBed.get(MyEffect);
userServiceSpy = TestBed.get(MyService);
});
This works perfectly
it('should return successActionsuccessAction', () => {
const action = actionOne();
const outcome = successAction({ payload: data });
actions$ = hot('-a', { a: action });
const response = cold('-a|', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
This doesn't work
it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
const outcome = successAction({ payload: data });
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a-a', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
There are two problems in this code.
It suggests that getSomeDataViaHTTP returns two values. This is wrong, the response is no different from your first example: '-a|'
It expects the second successAction to appear after 40 ms (--b--b, count the number of dashes). This is not correct, because actionTwo happens after 20 ms (-a-a) and response takes another 10 ms (-a). So the first successAction is after 20ms (10+10), the second is after 30ms (20+10). The marble is: '--b-b'.
Input actions : -a -a
1st http response : -a
2nd http response : -a
Output actions : --b -b
The working code:
it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a|', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const outcome = successAction({ payload: data });
const expected = cold('--b-b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
Marble testing is cool but it involves some black magic you should prepare for. I'd very much recommend you to carefully read this excellent article to have a deeper understanding of the subject.

Test resolution is blocked when the expected condition fails

With Protractor, and using Mocha framework, I am comparing two arrays of values, one from a bar chart, one from text fields.
The code looks like this:
it("Each bar should have a value equal to its percentage", () => {
var identicalValue: boolean = false;
helper.getFirstValues(textLocator).then((textValue) => {
helper.getBarValues(barLocator).then((barValue) => {
identicalValue = helper.compareArray(textValue, barValue);
//compareArray returns a boolean, true if the arrays have the same values
expect(identicalValue).to.equal(true);
});
});
});
the functions are coded this way:
public getFirstValues(factsheetTab: protractor.ElementFinder): webdriver.promise.Promise<{}> {
var deferred = protractor.promise.defer();
factsheetTab.all(by.tagName("tr")).map((ele, index) => {
return {
index: index,
text: ele.all(by.tagName("td")).first().getText()
}
}).then((topValue) => {
deferred.fulfill(topValue);
},
(reason) => { deferred.reject(reason) });
return deferred.promise;
};
public getBarValues(factsheetTab: protractor.ElementFinder): webdriver.promise.Promise<{}> {
var deferred = protractor.promise.defer();
factsheetTab.all(by.tagName("tr")).map((ele, index) => {
return {
index: index,
text: ele.all(by.tagName("td")).last().element(by.tagName("div")).getAttribute("data-value")
}
}).then((barValue) => {
deferred.fulfill(barValue);
},
(reason) => { deferred.reject(reason) });
return deferred.promise;
};
My problem is that when the comparison returns false, so when the two arrays have differences, the test is blocked. It doesn't fail, the browser remains opened on that step, and the process stops, ignoring the remaining tasks.
Note: the function helper.compareArray returns a correct value. I could also write "expect(false).to.equal(true)" and get blocked too.
Am I doing something wrong in this test? Do you see a reason why this test is not finished?
edit: I found a workaround, to not get stuck in case of the test failing:
it("Each bar should have a value equal to its percentage", () => {
var identicalValue: boolean = false;
var textValue = null;
helper.getFirstValues(textLocator).then((value) => {
textValue = value;
});
helper.getBarValues(barLocator).then((barValue) => {
chai.assert.deepEqual(barValue, textValue);
});
});
(using #Brine's suggestion, for the deepEqual)
This seems to work, the other tests are ran if this one fails.
I'm still curious to know what was wrong with the first version though.
Not sure if this is a bug in your helper.compareArray or Mocha... but why not compare the arrays directly? I don't use Mocha but it seems something like this would work:
expect(textValue).deepEqual(barValue);