Concat multilevel API calls in Angular using rxjs - arrays

I am new to rxjs I am calling three api calls using the hacker new api. I want to concat response of the last api call to the second api call.
I want to achieve something like this.
[
{
"by": "john"
"title": "Sample title"
"time":1657786985
"karma": 123456 // this is from the third api call that i want to merge
},
{
"by": "jane"
"title": "Sample title 2"
"time":1657786333
"karma": 123456 // this is from the third api call that i want to merge
}
]
So far this is my code
this.service.fetchStories().pipe( // get list of id array
switchMap(idArray => forkJoin(
idArray.map(id => this.service.fetchStoryItems(id).pipe( // first api call
concatMap(itm => this.service.fetchUserById(itm.by)) // second api call i want to merge { karma } to the first api call
)))
),
take(1)
).subscribe(data => console.log(data));
But the result is I am getting only the response of the last api call. Can somebody help me. I am trying to learn rxjs.

All you need to do is to pipe the third call again and merge the results.
this.service.fetchStories().pipe( // get list of id array
switchMap(idArray => forkJoin(
idArray.map(id => this.service.fetchStoryItems(id).pipe( // first api call
concatMap(itm => this.service.fetchUserById(itm.by).pipe(map(({karma}) => ({ ...itm, karma }))))
)))
),
take(1)
).subscribe(data => console.log(data));

Related

Change ID of a Axios request with one useEffect

I have a little problem.
I want to make a call to the api like this:
const API = `https://api.com/id=${myjson.id}`
useEffect(() => {
axios
[...]
}, []);
But I'd like to make my URL ID (which I get from a JSON file) change with each useEffect.
My Json :
[
{"id":"5200"},
{"id":"5204"},
]
The goal is to be able to map the result using useEffect only once.
Example :
The result of my first request 5200.
The result of my second request 5204.
Is that possible ?
Thank you very much and have a nice day =) !

Nested .map using redux-sagas call

I need to make a series of api calls, defined by 2 parameters. Let's call them users and foods. Each is an array of strings. For each user, and for each food, I need to construct a unique api call, which calls a route. I have a utility saga to do this:
function* getUserFoodDetails(requestParams) {
const { user, food } = requestParams
const response = yield call(
axios.get,
`api/foodstats/${user}/${food}`,
{
params: {
startDate: 'some datestring',
endDate: 'some datestring'
}
}
);
return response;
}
(Yes, I know this API design is not great, but its what I have to work with.) This saga can call one route at a time. I read redux-saga: How to create multiple calls/side-effects programmatically for yield?, and I had once asked my own question Getting results from redux-saga all, even if there are failures for how to implement error handling in this type of scenario. The general consensus was to use the utility saga with yield all to call many routes at once. If there was only one parameter, we could do this
const responses = yield users.map(user => call(getUserDetails, { user }));
And we would end up with an array of user data mapped from the original users array.
Now I am in a situation where I have a 2-dimensional dataset, and I need to make a call for every combination of user and food. For example,
const users = ['me', 'you', 'someone'];
const foods = ['bananas', 'oranges', 'apples'];
I am working within an existing app and I need to conform to the end-result data structure. The structure the rest of the UI expects is an array of arrays of result data. The first level array corresponds to each user, and the next level corresponds to each user's food data. (While I may want to change this, there's only so much legacy code refactoring I want to deal with at once). The structure should end up like this:
[
[ // 'me' data:
{ noEaten: 44, enjoyment: 5 }, // 'me' 'bananas' data
{ noEaten: 14, enjoyment: 2 }, // 'me' 'oranges' data
{ noEaten: 22, enjoyment: 4 }, // 'me' 'apples' data
],
[ // 'you' data:
{ noEaten: 12, enjoyment: 2 }, // 'you' 'bananas' data
{ noEaten: 334, enjoyment: 12 }, // 'you' 'apples' data
],
[ // 'someone' data
{ noEaten: 14, enjoyment: 2 }, // 'someone' 'oranges' data
{ noEaten: 22, enjoyment: 4 }, // 'someone' 'apples' data
]
]
Its not a very semantic structure, but its what I'm trying to get to.
The previous code achieves this with a nested map call within promises
const allUsers = user.map(user =>
const all = foods.map(food =>
fetch(`api/foodstats/${user}/${food}`)
.then(res => res.json())
)
return Promise.allSettled(all)
)
Promise.all(allUsers).then(res => doSomethingWithData(res));
This nesting of Promise.all and allSettled is very strange to me, but it does result in the above data structure.
I need to recreate this with sagas. Within my saga, I try to do a nested map as well:
const data = yield all(
users.map(user =>
foods.map(food =>
call(getUserFoodDetails, {
user,
food
})
)
)
);
However this does't work. I have 2 layers deep of .map, but only 1 layer deep of all and call. what I end up with an array of redux saga objects:
While I understand why this is happenining, I'm not sure how to fix it.
How can I nest .map statements with redux sagas, such that the api calls are made, and the data is returned in the same nested structure with which I made the calls? Is this possible? Is it worth bothering? Or is it better to come up with some intermediate data structures to obtain a single-layer array, and then restructure back to the 2-layer array that's needed in the components?
The solution was right in front of me - I'll leave it here in case anyone ever needs something similar. If a single call to yield all(users.map(() => {)) gives a 1-layer array of data, then I needed a 2-layer call to yield all to get a 2 layer array of data. Its a little convoluted, but 2 utiliy sagas, each which takes a single parameter:
// Utility saga to call route once, for 1 user's single food
function* getFoodDetails(requestParams) {
const { user, food, params } = requestParams;
const response = yield call(
axios.get,
`api/foodstats/${user}/${food}`,
{ ...params }
);
return response;
}
// Utility saga to call route for every food for a single user
function* getUserDetails(requestParams) {
const { user, foods, params } = requestParams;
yield all(foods.map(food =>
call(getFoodDetails, { user, food, params })
))
return response;
}
// Saga to call route for every user
function* getAllUserFoodData(requestParams) {
const { users, foods, params } = requestParams;
const response = yield call(
yield all(users.map(user =>
call(getUserDetails, { user, foods, params })
))
);
return response;
}
So getAllUserFoodData maps over all users, creating the first level of the array, which is one item per user. In each of those map calls, getUserDetails maps over each food for a given user, creating the second level of the array, which is one item per food. Finally getFoodDetails is called, which calls the route for a single user's single food.

How to mutate specific object in an array using SWR

I have the following array
[
{
"idChatPublic": 17,
"idChatRoom": 2,
"idSender": "6c25110d-4256-42e1-8205-b75ece274487",
"username": "Hudson Thadeu Teixeira",
"message": "hello",
"avatar": null,
"createdAt": "12:43",
"chatLike": []
},
{
"idChatPublic": 33,
"idChatRoom": 2,
"idSender": "6c25110d-4256-42e1-8205-b75ece274487",
"username": "Hudson Thadeu Teixeira",
"message": "jam",
"avatar": null,
"createdAt": "13:07",
"chatLike": [
{
"idChatLike": 1,
"idChatPublic": 33,
"idUser": "",
"createdAt": "2022-02-14T08:59:34.000Z"
}
]
}
]
How can mutate an specific object of this array and add an object
to the "chatLike" array using SWR?
I have the following function:
async function sendLike() {
const newLike = {
idUser: myUser.userId,
}
mutate(
async (data) => {
console.log(data[messageIndex]) // This log returns specific object in the array
// Handle mutation
},
false
)
socket.emit('send_like', newLike)
}
Please guys I've been trying this for a while would be great If someone gives me a hand :D
You're using SWR with a websocket, which is an anti-pattern. SWR is meant for managing data that's fetched via REST or GraphQL requests, and it can be configured to refetch data on a regular interval. It's not a real time connection. Websocket on the other hand is real time. Consider if SWR is really best for your project - you probably don't need it.
Anyway, I also noticed some issues with how your mutate() is written so here's some feedback.
you must pass an ID into first argument
if you pass a function into mutate's second argument, it must return the new data
an easy way to update a specific item in an array is by using .map() and spread ... syntax
function sendLike() {
const newLike = {
idUser: myUser.userId,
}
// send request to update the source
// this is usually an REST request such as 'POST, UPDATE' etc
socket.emit('send_like', newLike)
// mutate local data
mutate(
'/api/chat', // ID must match ID used in the useSWR hook,
data => data.map(chat => chat.idChatPublic === idChat : {
...chat,
chatLike: [
...chat.chatLike
newLike
]
} : chat),
false
);
}

PHP Laravel Sage One API says invoice lines are required when they are there and not empty

I am currently integrating an application with the Sage One API and am having a problem. The API says that invoice lines may not be empty, but the data is there. What am I doing wrong?
Here is my method of getting the line items:
$lineItems = [];
foreach ($invoiceItems as $invoiceItem){
$lineItems[] =
[
"SelectionId" => "35175771",
"TaxTypeId" => "6194291",
"Description" => $invoiceItem->name,
"Quantity" => $invoiceItem->duration,
"UnitPriceExclusive" => $invoiceItem->total_excl_vat
];
}
foreach ($invoiceOtherItems as $invoiceOtherItem){
$lineItems[] =
[
"SelectionId" => "35175771",
"TaxTypeId" => "6194291",
"Description" => $invoiceOtherItem->otherItem->name,
"Quantity" => $invoiceOtherItem->quantity,
"UnitPriceExclusive" => $invoiceOtherItem->total_excl_vat
];
}
//dd($lineItems)
And here is the part in the post data to the API where I post the invoice items (removed majority items for the sake of brevity here):
$invoice = [
"Date" => Carbon::now()->toDateString(),
"Lines" => $lineItems,
"DueDate" => "2021-08-29"
];
Performing a dump and die where I commented the dd returns all the arrays, yet the API is telling me lines are required. Am I doing anything wrong? The code seems correct to me and I can't find anything to help on this matter.
For anyone that might run across this problem here is the solution:
In your response, define your headers like this:
'headers' => ['Content-Type' => 'application/json', 'Accept' => 'application/json']
And do not use form_params, but use this instead:
'body' => json_encode($invoice)

Make Axios Limit the Number of Responses

I am attempting to make an axios GET call to an API to retrieve some JSON data. The data is stored in an array with multiple objects each containing the same keys. There are hundreds of objects in this array but I only want to return the first ten. I am aware I can truncate the data set once it is returned but I am wondering if there is a way to limit the amount of responses to my API call.
Here is an example of the data set:
[
{
"userId": 1,
"id": 1,
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald"
},
{
"userId": 1,
"id": 2,
"title": "1984",
"author": "George Orwell"
},
{
"userId": 1,
"id": 3,
"title": "The Count of Monte Cristo",
"author": "Alexandre Dumas"
},
]
and my axios request is simple as well:
router.get('/', (req, res) => {
axios.get(`https://jsonwebsit.info.com/posts`)
.then( response => {
res.send(response.data)
})
.catch(err => {
console.log('Error getting all data from API', err)
})
});
Actually, you can limit the number of responses from a certain API endpoint.
Just add params as an object as a second parameter while making the request.
For example:
componentDidMount() {
axios.get('https://jsonplaceholder.typicode.com/todos',{
params: {
_limit: 10
}
})
.then((res) => {
this.setState({
todos: res.data
});
})
}
Here you are limiting the response to 10 from jsonplaceholder API.
You could also call https://jsonplaceholder.typicode.com/todos/?_limit=10 and would get the same result.
Hope it helps.
On your side there's nothing you can do until pagination is implemented on API side. Depending on the way it's implemented, you will probably make requests to API sending params like offset, page, limit- these are the most common params' names saying how many elements should API return. But if that's 3rd party provider and their docs are not saying anything about such possibility, you're most likely won't be able to do what you want

Resources