Why does for loop not work in the reducer? - react-redux

The reducer works normally if i remove the for loop in the case ADD_TO_CART but once I add it, it goes to case remove_from_cart whereas, I have no action creator for the remove_from_cart.
const init = [];
const addToCartReducer = (state = init, action) => {
const newData = action.payload;
switch (action.type) {
case ADD_TO_CART:
for (let i = 0; i < state.length; i += 1) {
if (newData.id) {
return [...state, action.payload];
} else {
return [...state];
}
}
case REMOVE_FROM_CART:
console.log(newData)
const filter = state.filter((product) => product.id !== newData.id);
return [...filter];
default:
return state;
}
};
whenever I comment the forloop, it works but I have to do some checks before I send to the cart.
What do you think is wrong here, please?

Related

execute a sequence of GET calls to an API wait and do some treatments on the results then give the result as argumant to another methode for a POST

I am new to Angular and i am facing some difficulties with a task. I have an array of IDs that i want to execute the same GET Call over. And for every GET call result i have to do some operations and then add the result of every operation to some arrays. I managed to find a way to do it correctly. But my problem is, i can't manage to wait for the final result to be ready (after all the GET calls are done and the operations too) before giving it as an argument to another method that will send it with a POST call.
the method where i do the GET calls and the operations over every call's result (the problem occurs when i am in the rollBackSPN condition).
async getComponentIds(taskName: String, selectedComponents: IComponent[]) {
const componentsId: number[] = [];
const componentsWithoutParams: IComponent[] = [];
let sendPortaPrecedente : boolean;
if(taskName == "rollBackSPN"){
from(selectedComponents).pipe(
concatMap(component =>{
return this.http.get<any>("Url"+component.idComponent).pipe(
tap(val => {
sendPortaPrecedente = true;
for(const obj of val){
if((obj.name == "z0bpqPrevious" && obj.value == null) || (obj.name == "datePortaPrevious" && obj.value == null) || (obj.name == "typePortaPrevious" && obj.value == null)){
sendPortaPrecedente = false;
}
}
if(sendPortaPrecedente){
componentsId.push(component.idComponent);
}else{
componentsWithoutParams.push(component);
}
}),
catchError(err => {
return of(err);
})
)
})
).subscribe(val => {
return { componentsId : componentsId, componentsWithoutParams : componentsWithoutParams, sendPortaPrecedente : sendPortaPrecedente};
});
}else{
for (const component of selectedComponents) {
componentsId.push(component.idComponent)
return { componentsId : componentsId, componentsWithoutParams : componentsWithoutParams, sendPortaPrecedente : sendPortaPrecedente};
}
}
}
The method where i pass the getComponentIds(taskName: String, selectedComponents: IComponent[]) result so it can be send with a POST call (again when i am in the rollBackSPN condition)
executeTask(serviceIdSi: string, actionIdSi: string, actionClassName: string, componentName: string, taskName: string,
componentsId: number[], componentsWithoutParams: IComponent[], sendPortaPrecedente: boolean): Observable<any> {
const url = this.taskUrl + `?serviceId=${serviceIdSi}` + `&actionId=${actionIdSi}` + `&actionClassName=${actionClassName}`
+ `&componentName=${componentName}` + `&taskName=${taskName}`;
if(taskName == "rollBackSPN"){
if(sendPortaPrecedente && componentsWithoutParams.length == 0){
return this.http.post<any>(url, componentsId);
}else{
let errMessage = "Some Error Message"
for(const component of componentsWithoutParams){
errMessage = errMessage + component.idComponent +"\n";
}
throw throwError(errMessage);
}
}else{
return this.http.post<any>(url, componentsId);
}
}
Both these methods are defined in a service called TaskService.
And the service is called like this in a component UnitTaskButtonsComponent.
async launchUnitTask() {
this.isLoading = true;
this.isClosed = false;
this.appComponent.currentComponentIndex = this.componentIndex;
let res = await this.taskService.getComponentIds(this.unitTaskLabel, this.selectedComponents);
this.taskService.executeTask(this.appComponent.currentService.identifiantSi,
this.appComponent.currentAction.identifiantSi,
this.appComponent.currentAction.className,
this.selectedComponents[0].name,
this.unitTaskLabel,
res.componentsId,
res.componentsWithoutParams,
res.sendPortaPrecedente).subscribe(
data => this.executeTaskSuccess(),
error => this.executeTaskError());
}
"res" properties are always undefined when it's a rollBackSPN task.
The main issue here is that getComponentIds does not return a Promise. So awaiting does not work. I would suggest to change getComponentIds so that it returns an Observable instead.
getComponentIds(taskName: string, selectedComponents: IComponent[]) {
// ^^^^^^ use string instead of String
return forkJoin(
selectedComponents.map((component) => {
return this.http.get<any>("Url" + component.idComponent).pipe(
map((val) => {
let sendPortaPrecedente = true;
for (const obj of val) {
if (
(obj.name == "z0bpqPrevious" && obj.value == null) ||
(obj.name == "datePortaPrevious" && obj.value == null) ||
(obj.name == "typePortaPrevious" && obj.value == null)
) {
sendPortaPrecedente = false;
}
}
return { component, sendPortaPrecedente }
}),
catchError((err) => of(err))
);
})
).pipe(
map((result) => {
const componentsId: number[] = [];
const componentsWithoutParams: IComponent[] = [];
for (const val of result) {
if (val.sendPortaPrecedente) {
componentsId.push(val.component.idComponent);
} else {
componentsWithoutParams.push(val.component);
}
}
return { componentsId, componentsWithoutParams };
})
);
}
Instead of using concatMap, let's use a forkJoin. The forkJoin allows sending all requests in parallel and returns the result in an array. But we have to pass in an array of Observables. That's why we map over the selectedComponents.
In the lower map, we can now get the complete result of the http calls in the result parameter. Here we do the processing of the data. I was not really sure how to handle the sendPortaPrecedente. You will have to fill that in.
We simply return the whole Observable
async launchUnitTask() {
this.taskService
.getComponentIds(this.unitTaskLabel, this.selectedComponents)
.pipe(
switchMap((res) => {
this.taskService
.executeTask(
this.appComponent.currentService.identifiantSi,
this.appComponent.currentAction.identifiantSi,
this.appComponent.currentAction.className,
this.selectedComponents[0].name,
this.unitTaskLabel,
res.componentsId,
res.componentsWithoutParams,
res.sendPortaPrecedente
)
})
).subscribe(
(data) => this.executeTaskSuccess(),
(error) => this.executeTaskError()
);
}
In the launchUnitTask method, we don't use await anymore. Instead, we call getComponentIds and chain the call of executeTask with a switchMap.

Cypress: I need to exit the for loop if the condition is satisfied

Below is the code which I am using.
I am working with Cypress + Cucumber + Typescript.
Scenario: I need to get a list of unique values using a for loop. Then I am passing this value to an API to verify some condition and if the condition is met I want to exit the loop.
To exit the loop I somewhere read a solution that if I use "return false" as first-line in if condition then loop will exit which seems to work fine.
The issue here is, when I try to set a flag from inside the for-if loop to the instance variable then the value read by if condition (for exiting the loop) is not picking the updated value of instance variable. And the loop continues to run.
Below is the code snippet:
class test {
static isVinavailable: boolean = false;
static setEligibleVehicleVinTest() {
cy.xpath(eligibleForSaleVehicleVin).then((esv) => {
const listingCount = Cypress.$(esv).length;
for (let i = 0; i < listingCount; i++) {
let text123 = esv.eq(i).text();
genericAction.getAuthenticationKey();
cy.fixture("authResp.json")
.then((authResp) => {
cy.request({
method: "GET",
url: vehicleCheckEligibility + text123,
headers: {
Authorization: authResp.access_token,
},
});
})
.then((response: any) => {
cy.wait(5000);
let responseDataelig = response.body;
if (
(responseDataelig.val1 =
"Y" &&
responseDataelig.val2 === "N" &&
responseDataelig.val3 === "N")
) {
this.isVinavailable = true;
}
});
if (this.isVinavailable) {
return false;
}
}
});
}
}
class test {
static isVinavailable = false;
static setEligibleVehicleVinTest(): Cypress.Chainable<boolean> {
return cy.xpath(eligibleForSaleVehicleVin).each(($el) => {
let text123 = $el.text();
cy.fixture('authResp.json')
.then((authResp) => {
return cy.request({
// your code block
});
})
.then((response: any) => {
// your code block
if (condition) {
this.isVinavailable = true;
return false;
};
});
}).then(() => {
return this.isVinavailable;
});
}
}

How can I capture all the values of the dropdown list in Cypress?

I have a dropdown box that displays the list of States. There are around 40 States in the list.
Every time when I scroll down the list, the List displays only 15 to 20 States at a time.
I want to capture all the values of the list and save them in the string array. And then check alphabet sorting.
How can I do it using Cypress? Currently, It captures only the top 15 items from the list.
This is my code:
const verifySortOrdering = (key: string) =>
getSingleSelectList(key).then(dropdown => {
cy.wrap(dropdown).click();
if (dropdown.length > 0) {
const selector = 'nz-option-container nz-option-item';
let NumOfScroll = 1;
const unsortedItems: string[] = [];
const sortedItems: string[] = [];
cy.get(selector).then((listItem) => {
while (NumOfScroll < 7) {
sortAndCheck(selector, unsortedItems, sortedItems);
if (listItem.length < 15) {
break;
}
NumOfScroll++;
}
});
}
});
const sortAndCheck = (selector: string, unsortedItems: any, sortedItems: any) => {
cy.get(selector).each((listItem, index) => {
if (index === 15) {
cy.wrap(listItem).trigger('mousedown').scrollIntoView().last();
}
unsortedItems.push(listItem.text());
sortedItems = unsortedItems.sort();
expect(unsortedItems, 'Items are sorted').to.deep.equal(sortedItems);
});
};
Here's a working example based off of what you provided. Added an additional check to ensure the list has the right amount of options. You may or may not want that. Deep copying the unsorted list so it doesn't get sorted due to a shallow copy. Added validations after the unsortedItems list gets built so we can validate once instead of for every item in the list.
var unsortedItems = new Array()
var expectedListCount = 32
cy.get('#myselect>option').should('have.length', expectedListCount)
.each(($el) => {
unsortedItems.push($el.text());
}).then(() => {
var sortedItems = [...unsortedItems]; // deep copy
sortedItems.sort();
expect(unsortedItems).to.deep.equal(sortedItems)
})
Another example based on your revised sample but I can't verify it without having a working example of your DDL. This builds up the unsortedItem list first and then does the comparison.
const verifySortOrdering = (key: string) =>
getSingleSelectList(key).then(dropdown => {
cy.wrap(dropdown).click();
if (dropdown.length > 0) {
const selector = 'nz-option-container nz-option-item';
let NumOfScroll = 1;
const unsortedItems: string[] = [];
const sortedItems: string[] = [];
cy.get(selector).then((listItem) => {
while (NumOfScroll < 7) {
unsortedListBuilder (selector, unsortedItems, sortedItems);
if (listItem.length < 15) {
break;
}
NumOfScroll++;
}
});
var sortedItems = [...unsortedItems];
sortedItems.sort();
expect(unsortedItems).to.deep.equal(sortedItems);
}
});
const unsortedListBuilder = (selector: string, unsortedItems: any, sortedItems: any) => {
cy.get(selector).each((listItem, index) => {
if (index === 15) {
cy.wrap(listItem).trigger('mousedown').scrollIntoView().last();
}
unsortedItems.push(listItem.text());
});
};

redux-saga, websockets and actions queue

I have the following problem: server send's messages to the client through websocket. on the client, I need to display this messages to the user. but the problem is that sometimes messages come to fast, and I need to organize some sort of queue and show that messages one after another.
my saga:
import { eventChannel, effects, takeEvery } from 'redux-saga';
import { types, actionCreators } from './actions';
const { call, put, take, race } = effects;
function watchMessages(socket) {
return eventChannel((emitter) => {
socket.onopen = (e) => (emitter(actionCreators.socketOpen(e)));
socket.onclose = (e) => (emitter(actionCreators.socketClose(e)));
socket.onerror = (e) => (emitter(actionCreators.socketError(e)));
socket.onmessage = (e) => (emitter(actionCreators.socketMessage(e)));
return () => {
socket.close();
};
});
}
function* internalListener(socket) {
while (true) {
const data = yield take(types.SOCKET_SEND);
socket.send(data.payload);
}
}
function* externalListener(socketChannel) {
while (true) {
const action = yield take(socketChannel);
yield put(action);
}
}
function* wsHandling(action) {
const socket = action.payload.socket;
while (true) {
const socketChannel = yield call(watchMessages, socket);
const { cancel } = yield race({
task: [call(externalListener, socketChannel), call(internalListener, socket)],
cancel: take(types.SOCKET_CLOSE),
});
if (cancel) {
socketChannel.close();
}
}
}
export default function* rootSaga(action) {
yield takeEvery(types.SOCKET_CONNECT, wsHandling);
}
my reducer:
function dataReducer(state = initialStateData, action) {
switch (action.type) {
case types.SOCKET_MESSAGE:
if (action.payload.channel === 'channel1') {
return state
.set('apichannel1', action.payload);
} else if (action.payload.channel === 'channel2') {
return state
.set('apichannel2', action.payload);
} else if (action.payload.channel === 'channel3') {
return state
.set('apichannel3', action.payload);
}
return state;
default:
return state;
}
}
so now, when the new message arrives, I'm changing state and just display it on the screen.
any ideas how I can turn this into the following: put arrived messages into some sort of queue, and show them one by one on screen for some custom time?
You can buffer (queue) actions in redux saga using the actionChannel effect.
https://redux-saga.js.org/docs/advanced/Channels.html#using-the-actionchannel-effect
Then you can read from the channel's buffer at your own speed.
I created a simplified example that listens for messages and displays max 3 at a time, each for 5 seconds. See:
https://codesandbox.io/s/wt8uu?file=/src/index.js
At the bottom, there is a console panel, use it to call addMsg('Msg: ' + Math.random()) to simulate new socket.io message.
so I did it this way, here is the code, maybe it will be useful for someone
let pendingTasks = [];
let activeTasks = [];
function watchMessages(socket) {
return eventChannel((emitter) => {
socket.onopen = (e) => (emitter(actionCreators.socketOpen(e)));
socket.onclose = (e) => (emitter(actionCreators.socketClose(e)));
socket.onerror = (e) => (emitter(actionCreators.socketError(e)));
socket.onmessage = (e) => (emitter(actionCreators.socketMessage(e)));
return () => {
socket.close();
};
});
}
function* internalListener(socket) {
while (true) {
const data = yield take(types.SOCKET_SEND);
socket.send(data.payload);
}
}
function* externalListener(socketChannel) {
while (true) {
const action = yield take(socketChannel);
pendingTasks = [...pendingTasks, action];
}
}
function* wsHandling(action) {
const socket = action.payload.socket;
while (true) {
const socketChannel = yield call(watchMessages, socket);
const { cancel } = yield race({
task: [call(externalListener, socketChannel), call(internalListener, socket)],
cancel: take(types.SOCKET_CLOSE),
});
if (cancel) {
socketChannel.close();
}
}
}
function* tasksScheduler() {
while (true) {
const canDisplayTask = activeTasks.length < 1 && pendingTasks.length > 0;
if (canDisplayTask) {
const [firstTask, ...remainingTasks] = pendingTasks;
pendingTasks = remainingTasks;
yield fork(displayTask, firstTask);
yield call(delay, 300);
}
else {
yield call(delay, 50);
}
}
}
function* displayTask(task) {
activeTasks = [...activeTasks, task];
yield put(task);
yield call(delay, 3000);
activeTasks = _.without(activeTasks, task);
}
export default function* rootSaga(action) {
yield [
takeEvery(types.SOCKET_CONNECT, wsHandling),
takeEvery(types.SOCKET_CONNECT, tasksScheduler),
];
}

How to list folders and files in a directory using ReactiveX

When using Observables for certain tasks that involve a lot of chaining and a lot of asynchronous operations, such as listing all the items in a folder and checking all of the folders in it for a specific file, I often end up either needing to build the complex chain for each task (return Observable.of(folder)...) or having some kind of special value that gets forwarded to the end to signal the end of a batch (every operator starts with if(res === false) return Observable.of(false)).
Sort of like that stick that you put between your groceries and those of the person in front of you at the checkout.
It seems like there should be a better way that doesn't involve forwarding a stop value through all kinds of callbacks and operators.
So what is a good way to call a function that takes a folder path string and returns a list of all the files and folders in it. It also specifies whether the files are HTML files or not, and whether or not the folders contain a file called tiddlywiki.json.
The only requirement is that it can't return anything like Observable.of(...).... It should probably have a subject at the top of the chain, but that is not a requirement.
function listFolders(folder) {
return [
{ type: 'folder', name: 'folder1' },
{ type: 'datafolder', name: 'folder2' }, //contains "tiddlywiki.json" file
{ type: 'folder', name: 'folder3' },
{ type: 'htmlfile', name: 'test.html' },
{ type: 'other', name: 'mytest.txt' }
]
}
Here is one that does not follow the rules I layed out (see below for one that does), but it took about ten minutes, using the first one as a guide.
export function statFolder(subscriber, input: Observable<any>) {
return input.mergeMap(([folder, tag]) => {
return obs_readdir({ folder, tag })(folder);
}).mergeMap(([err, files, { folder, tag }]) => {
if (err) { return Observable.of({ error: err }) as any; }
else return Observable.from(files).mergeMap(file => {
return obs_stat([file,folder])(path.join(folder, file as string));
}).map(statFolderEntryCB).mergeMap<any, any>((res) => {
let [entry, [name, folder]] = res as [any, [string, string, number, any]];
if (entry.type === 'folder')
return obs_readdir([entry])(path.join(entry.folder, entry.name));
else return Observable.of([true, entry]);
}, 20).map((res) => {
if (res[0] === true) return (res);
let [err, files, [entry]] = res as [any, string[], [FolderEntry, number, any]];
if (err) {
entry.type = "error";
} else if (files.indexOf('tiddlywiki.json') > -1)
entry.type = 'datafolder';
return ([true, entry]);
}).reduce((n, [dud, entry]) => {
n.push(entry);
return n;
}, []).map(entries => {
return { entries, folder, tag };
}) as Observable<{ entries: any, folder: any, tag: any }>;
}).subscribe(subscriber);
}
Original: This took a few hours to write...and it works...but...it uses concatMap, so it can only take one request at a time. It uses a custom operator that I wrote for the purpose.
export function statFileBatch(subscriber, input: Observable<any>) {
const signal = new Subject<number>();
var count = 0;
//use set timeout to fire after the buffer recieves this item
const sendSignal = (item) => setTimeout(() => { count = 0; signal.next(item); });
return input.concatMap(([folder, tag]) => {
return obs_readdir({ folder, tag })(folder);
}).lift({
call: (subs: Subscriber<any>, source: Observable<any>) => {
const signalFunction = (count) => signal.mapTo(1), forwardWhenEmpty = true;
const waiting = [];
const _output = new Subject();
var _count = new Subject<number>()
const countFactory = Observable.defer(() => {
return Observable.create(subscriber => {
_count.subscribe(subscriber);
})
});
var isEmpty = true;
const sourceSubs = source.subscribe(item => {
if (isEmpty && forwardWhenEmpty) {
_output.next(item);
} else {
waiting.push(item)
}
isEmpty = false;
})
const pulse = new Subject<any>();
const signalSubs = pulse.switchMap(() => {
return signalFunction(countFactory)
}).subscribe(count => {
//act on the closing observable value
var i = 0;
while (waiting.length > 0 && i++ < count)
_output.next(waiting.shift());
//if nothing was output, then we are empty
//if something was output then we are not
//this is meant to be used with bufferWhen
if (i === 0) isEmpty = true;
_count.next(i);
_count.complete();
_count = new Subject<number>();
pulse.next();
})
pulse.next(); //prime the pump
const outputSubs = Observable.create((subscriber) => {
return _output.subscribe(subscriber);
}).subscribe(subs) as Subscription;
return function () {
outputSubs.unsubscribe();
signalSubs.unsubscribe();
sourceSubs.unsubscribe();
}
}
}).mergeMap(([err, files, { folder, tag }]) => {
if (err) { sendSignal(err); return Observable.empty(); }
return Observable.from(files.map(a => [a, folder, files.length, tag])) as any;
}).mergeMap((res: any) => {
let [file, folder, fileCount, tag] = res as [string, string, number, any];
return obs_stat([file, folder, fileCount, tag])(path.join(folder, file))
}, 20).map(statFolderEntryCB).mergeMap<any, any>((res) => {
let [entry, [name, folder, fileCount, tag]] = res as [any, [string, string, number, any]];
if (entry.type === 'folder')
return obs_readdir([entry, fileCount, tag])(path.join(entry.folder, entry.name));
else return Observable.of([true, entry, fileCount, tag]);
}, 20).map((res) => {
//if (res === false) return (false);
if (res[0] === true) return (res);
let [err, files, [entry, fileCount, tag]] = res as [any, string[], [FolderEntry, number, any]];
if (err) {
entry.type = "error";
} else if (files.indexOf('tiddlywiki.json') > -1)
entry.type = 'datafolder';
return ([true, entry, fileCount, tag]);
}).map(([dud, entry, fileCount, tag]) => {
count++;
if (count === fileCount) {
sendSignal([count, tag]);
}
return entry;
}).bufferWhen(() => signal).withLatestFrom(signal).map(([files, [sigResult, tag]]: any) => {
return [
typeof sigResult !== 'number' ? sigResult : null, //error object
files, //file list
typeof sigResult === 'number' ? sigResult : null, //file count
tag //tag
];
}).subscribe(subscriber);
}

Resources