my api needs time to process a request, how can I use React + SWR to continue checking on the status? - node.js

I have an endpoint in my API that initiates a process on AWS. This process takes time. It can last several seconds or minutes depending on the size of the request. As of right now, I'm rebuilding my app to use swr. However, before this new update with swr I created a recursive function that would call itself with a timeout and continuously ping the API to request the status of my AWS process, only exiting once the response had the appropriate type.
I'd like to dump that recursive function because, well ... it was kinda hacky. Though, I'm still getting familiar with swr and I'm not a NodeJS API building master so I'm curious what thoughts come to mind in regards to improving the pattern below.
Ideally, the lowest hanging fruit would be to set up swr in some way to handle the incoming response and keep ping if the response isn't type: "complete" but I'm not sure how I'd do that. It pretty much just pings once and shows me whatever status it found at that time.
any help is appreciated!
tldr;
how can I set up swr to continually ping the API until my content is finished loading?
part of my API that sends out responses based how far along the AWS process is:
if (serviceResponse !== undefined) {
// * task is not complete
const { jobStatus } = serviceResponse.serviceJob;
if (serviceJobStatus.toLowerCase() === 'in_progress') {
return res.status(200).send({ type: 'loading', message: serviceJobStatus });
}
if (serviceJobStatus.toLowerCase() === 'queued') {
return res.status(200).send({ type: 'loading', message: serviceJobStatus });
}
if (serviceJobStatus.toLowerCase() === 'failed') {
return res.status(400).send({ type: 'failed', message: serviceJobStatus });
}
// * task is complete
if (serviceJobStatus.toLowerCase() === 'completed') {
const { serviceFileUri } = serviceResponse.serviceJob?.Data;
const { data } = await axios.get(serviceUri as string);
const formattedData = serviceDataParser(data.results);
return res.status(200).send({ type: 'complete', message: formattedData });
}
} else {
return res.status(400).send({ type: 'error', message: serviceResponse });
}
}
my current useSWR hook:
const { data: rawServiceData } = useSwr(
serviceEndpoint,
url => axios.get(url).then(r => r.data),
{
onSuccess: data => {
if (data.type === 'complete') {
dispatch(
setStatus({
type: 'success',
data: data.message,
message: 'service has been successfully generated.',
display: 'support-both',
})
);
dispatch(setRawService(data.message));
}
if (data.type === 'loading') {
dispatch(
setStatus({
type: 'success',
data: data.message,
message: 'service job is in progress.',
display: 'support-both',
})
);
}
},
}
);

After some digging around, figured I'd use the refreshInterval option that comes with swr. I am changing the state of a boolean on my component.
while the request is 'loading' the boolean in state is false.
once the job is 'complete' the boolean in state is set to true.
there is a ternary within my hook that sets the refreshInterval to 0 (default:off) or 3000.
const [serviceJobComplete, setServiceJobComplete] = useState(false);
const { data: serviceData } = useSwr(
serviceEndpoint,
url => axios.get(url).then(r => r.data),
{
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
refreshInterval: serviceJobComplete ? 0 : 3000,
...
// other options
}
);
helpful resources:
https://github.com/vercel/swr/issues/182
https://swr.vercel.app/docs/options

Related

Push notification shows object Object even though i am setting the right value

I am trying to implement push notifications with react and nodejs using service workers.
I am having problem while i am showing notification to the user.
Here is my service worker code:
self.addEventListener('push', async (event) => {
const {
type,
title,
body,
data: { redirectUrl },
} = event.data.json()
if (type === 'NEW_MESSAGE') {
try {
// Get all opened windows that service worker controls.
event.waitUntil(
self.clients.matchAll().then((clients) => {
// Get windows matching the url of the message's coming address.
const filteredClients = clients.filter((client) => client.url.includes(redirectUrl))
// If user's not on the same window as the message's coming address or if it window exists but it's, hidden send notification.
if (
filteredClients.length === 0 ||
(filteredClients.length > 0 &&
filteredClients.every((client) => client.visibilityState === 'hidden'))
) {
self.registration.showNotification({
title,
options: { body },
})
}
}),
)
} catch (error) {
console.error('Error while fetching clients:', error.message)
}
}
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
console.log(event)
if (event.action === 'NEW_MESSAGE') {
event.waitUntil(
self.clients.matchAll().then((clients) => {
if (clients.openWindow) {
clients
.openWindow(event.notification.data.redirectUrl)
.then((client) => (client ? client.focus() : null))
}
}),
)
}
})
When new notification comes from backend with a type of 'NEW_MESSAGE', i get the right values out of e.data and try to use them on showNotification function but it seems like something is not working out properly because notification looks like this even though event.data equals to this => type = 'NEW_MESSAGE', title: 'New Message', body: , data: { redirectUrl: }
Here is how notification looks:
Thanks for your help in advance.
The problem was i assigned parameters in the wrong way.
It should've been like this:
self.registration.showNotification(title, { body })

How do I hide error(s) after a few seconds? MERN stack

I wonder how to hide errors after a few seconds. For example, I have an API that must be passed a username, description, image, etc. All fields are required and if one field is missing I print the error 'All fields are required'. NodeJS takes care of that for me. It all works great, but I would like to hide that error in React after a few moments, and if I go to another page and go back to that page where the error was printed, it still stands. In React I use Redux, and for example my one action looks like this:
export const login = (email, password) => async dispatch => {
try {
dispatch({ type: USER_LOGIN_REQUEST });
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const { data } = await axios.post(
'/api/v1/user/login',
{ email, password },
config
);
dispatch({ type: USER_LOGIN_SUCCESS, payload: data });
localStorage.setItem('userInfo', JSON.stringify(data));
} catch (error) {
console.log(error.response.data.msg);
dispatch({
type: USER_LOGIN_FAIL,
payload: { msg: error.response.data.msg },
});
}
};
This is some of the most common logs of users and in
dispatch({
type: USER_LOGIN_FAIL,
payload: { msg: error.response.data.msg },
});
Prikazujem gresku koja mi se nalazi u error.response.data.msg .
This is my Reducer for that Action:
export const userReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return { ...state, loading: true };
case USER_LOGIN_SUCCESS:
return { ...state, loading: false, userInfo: action.payload };
case USER_LOGIN_FAIL:
return { ...state, loading: false, error: action.payload.msg };
case USER_LOGOUT:
return {};
default:
return state;
}
};
Reducer returns the error I print on the page.
It may be a bit of an extensive question, but I hope someone will help me. :)
If you want error to be disappeared after you left the screen, you can try to reset error variable in your state,
// When user leaves screen dispatch action with type of 'RESET_ERROR'
dispatch({
type: RESET_ERROR,
});
// Your reducer
export const userReducer = (state = {}, action) => {
switch (action.type) {
...
case RESET_ERROR:
return {...state, error: undefined}; // or null, whatever is your initialState is
default:
return state;
}
};
However, at first I would suggest (assuming you are not applying) to validate in client first. If it the UI contains any kind of form, go for formik or react-hook-form, otherwise do the client-side validation manually, then rely on your nodeJS server validation in the end.
If you really want to display error from the solely redux state, then you could reset error in redux state whenever user starts the action from scratch or leaves the screen or any other conditions you desire.

Microsoft teams task module bot framework , submit task handler is not working

The task module is not getting closed at and not returning to the bot.
Inside my Customform.html
async componentDidMount() {
const { headers, } = this.state;
microsoftTeams.initialize();
microsoftTeams.getContext((context, error) => {
this.setState( { context: context, }, () => { console.log( "this.state.context in upn is ", this.state.context["upn"] ); } ); }); }
Submit Handler
microsoftTeams.initialize();
if ( reservationResponse.status && reservationResponse.status === 400 ) {
this.setState({ showErrorMessage: true, loading: false, disableButton: false, }); }
else { this.setState({ loading: false, });
microsoftTeams.tasks.submitTask( reservationResponse, "bot-id" );
return true;
The bot id is correct and the url in the list of valid domains.
I have tried every possible combination , but it is not at all coming inside the handleTeamsTaskModuleSubmit  function.
componentDidMount() is a react component which will not work in html file. If you want to call getContext() in html file you need to follow below code format.
<script>
microsoftTeams.initialize();
microsoftTeams.getContext(function (context){console.log(context.entityId)});
</script>

Mock multiple api call inside one function using Moxios

I am writing a test case for my service class. I want to mock multiple calls inside one function as I am making two API calls from one function. I tried following but it is not working
it('should get store info', async done => {
const store: any = DealersAPIFixture.generateStoreInfo();
moxios.wait(() => {
const request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: store
});
const nextRequest = moxios.requests.at(1);
nextRequest.respondWith({
status: 200,
response: DealersAPIFixture.generateLocation()
});
});
const params = {
dealerId: store.dealerId,
storeId: store.storeId,
uid: 'h0pw1p20'
};
return DealerServices.retrieveStoreInfo(params).then((data: IStore) => {
const expectedOutput = DealersFixture.generateStoreInfo(data);
expect(data).toMatchObject(expectedOutput);
});
});
const nextRequest is always undefined
it throw error TypeError: Cannot read property 'respondWith' of undefined
here is my service class
static async retrieveStoreInfo(
queryParam: IStoreQueryString
): Promise<IStore> {
const res = await request(getDealerStoreParams(queryParam));
try {
const locationResponse = await graphQlRequest({
query: locationQuery,
variables: { storeId: res.data.storeId }
});
res.data['inventoryLocationCode'] =
locationResponse.data?.location?.inventoryLocationCode;
} catch (e) {
res.data['inventoryLocationCode'] = 'N/A';
}
return res.data;
}
Late for the party, but I had to resolve this same problem just today.
My (not ideal) solution is to use moxios.stubRequest for each request except for the last one. This solution is based on the fact that moxios.stubRequest pushes requests to moxios.requests, so, you'll be able to analyze all requests after responding to the last call.
The code will look something like this (considering you have 3 requests to do):
moxios.stubRequest("get-dealer-store-params", {
status: 200,
response: {
name: "Audi",
location: "Berlin",
}
});
moxios.stubRequest("graph-ql-request", {
status: 204,
});
moxios.wait(() => {
const lastRequest = moxios.requests.mostRecent();
lastRequest.respondWith({
status: 200,
response: {
isEverythingWentFine: true,
},
});
// Here you can analyze any request you want
// Assert getDealerStoreParams's request
const dealerStoreParamsRequest = moxios.requests.first();
expect(dealerStoreParamsRequest.config.headers.Accept).toBe("application/x-www-form-urlencoded");
// Assert graphQlRequest
const graphQlRequest = moxios.requests.get("POST", "graph-ql-request");
...
// Assert last request
expect(lastRequest.config.url).toBe("status");
});

Replicate EasyNetQ Request/Response with amqplib in nodeJS

I'm replicating EasyNetQ functionality in NodeJS (so that a Node app can communicate with over Rabbit with an EasyNetQ enabled .NET app). I've replicated EasyNetQ's Publish/Subscribe and EasyNetQ's Send/Receive, but i'm having some difficulty with EasyNetQ's Request/Response.
Here is my current Node code:
var rqrxID = uuid.v4(); //a GUID
var responseQueue = 'easynetq.response.' + rqrxID;
Q(Play.AMQ.ConfirmChannel.assertQueue(responseQueue, { durable: false, exclusive: true, autoDelete: true }))
.then((okQueueReply) =>
Play.AMQ.ConfirmChannel.consume(responseQueue, (msg) => {
//do something here...
Play.AMQ.ConfirmChannel.ack(msg);
})
)
.then((okSubscribeReply) => {
Q(Play.AMQ.ConfirmChannel.assertExchange('easy_net_q_rpc', 'direct', { durable: true, autoDelete: false }))
.then((okExchangeReply) =>
Play.AMQ.ConfirmChannel.publish(
global.AppConfig.amq.rpc.exchange,
dto.AsyncProcessorCommand.Type,
Play.ToBuffer(command),
{ type: command.GetType() },
(err, ok): void => {
if (err !== null) {
console.warn('Message nacked!');
responseDeferred.reject(err);
}
}
)
)
})
.catch((failReason) => {
console.error(util.format('Error creating response queue: %s', failReason));
return null;
});
Note that the publish works and is received by the .NET code. That code then sends a response and the issue is that the response isn't received. Here's the .NET code:
Bus.Respond<AsyncProcessorCommand, AsyncProcessorCommandResponse>(
request =>
{
Console.WriteLine("Got request: '{0}'", request);
return new AsyncProcessorCommandResponse()
{
ID = Guid.NewGuid(),
ResponseType = "ENQResp"
};
});
I'm sure I'm missing something, but not sure what. Who can help?
UPDATE
I have solved at least part of this. Taking the value of responseQueue and setting that into the options for publish as "replyTo" hooks the response up - nice. Now I just have to figure out how to either not create a new queue each time OR, make the response queue go away...
UPDATE FINAL
So, using the channel setup I had and saving the cinsumerTag (actually, specifying it) allowed me to cancel the consumer and the queue auto-deleted.
Taking my comments from above to answer this.
There are two pieces to this. First, from the code above, create your response queue so that it auto-deletes (when the consumer count drops to 0):
channel.assertQueue(responseQueue, { durable: false, exclusive: true, autoDelete: true }))
Then create/publish to the queue the "server" is listening on - making sure to set "replyTo" for the response queue you just created (the type piece is another bit of ENQ-needed code):
{ type: command.GetType(), replyTo: responseQueue }
So an entire (currently messy as it's "play" code) method for executing this pattern looks like:
private static Request(command: dto.AsyncProcessorCommand): Q.Promise<dto.interfaces.IAsyncProcessorCommandResponse> {
var responseDeferred = Q.defer<dto.interfaces.IAsyncProcessorCommandResponse>();
var consumerTag = uuid.v4();
var rqrxID = uuid.v4();
var responseQueue = 'easynetq.response.' + rqrxID;
var handleResponse = (msg: any): void => {
var respType = null;
switch(command.Action) {
default:
respType = 'testResp';
}
//just sending *something* back, should come from 'msg'
responseDeferred.resolve(new dto.AsyncProcessorCommandResponse(respType, { xxx: 'yyy', abc: '123' }));
}
Q(Play.AMQ.ConfirmChannel.assertQueue(responseQueue, { durable: false, exclusive: true, autoDelete: true }))
.then((okQueueReply) =>
Play.AMQ.ConfirmChannel.consume(responseQueue, (msg) => {
handleResponse(msg);
Play.AMQ.ConfirmChannel.ack(msg);
Play.AMQ.ConfirmChannel.cancel(consumerTag);
},
{ consumerTag: consumerTag })
)
.then((okSubscribeReply) => {
Q(Play.AMQ.ConfirmChannel.assertExchange('easy_net_q_rpc', 'direct', { durable: true, autoDelete: false }))
.then((okExchangeReply) =>
Play.AMQ.ConfirmChannel.publish(
'easy_net_q_rpc',
dto.AsyncProcessorCommand.Type,
Play.ToBuffer(command),
{ type: command.GetType(), replyTo: responseQueue },
(err, ok): void => {
if (err !== null) {
console.warn('Message nacked!');
responseDeferred.reject(err);
}
}
)
)
})
.catch((failReason) => {
console.error(util.format('Error creating response queue: %s', failReason));
return null;
});
return responseDeferred.promise
}

Resources