I'm working with an existing backend which I can not adjust anymore. I need to do a call which gets all messages. One of the params is type, this can be used to get all messages from a specific type. The backend developer had told me I had to use this multiple times looking like this:
/api/message?type=test&type=test2
So i tried sending it as a array, which resulted in:
/api/message?type[]=test&type[]=test2
Finding some information on SO made me use QS but this also doesn't give me the result I need
api/message?type%5B0%5D=test&type%5B1%5D=test2
export const getMessages = () =>
baseApi
.get('/message', {
params: {
type: [
'test',
'test2'
]
},
paramsSerializer: (params) => {
return qs.stringify(params)
}
})
.then((r) => r.data as Message[])
This isn't related to Axios. The qs library is responsible for generating that string. See its documentation:
When arrays are stringified, by default they are given explicit
indices:
qs.stringify({ a: ['b', 'c', 'd'] });
// 'a[0]=b&a[1]=c&a[2]=d'
You may override this by setting the indices option to false:
qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
// 'a=b&a=c&a=d'
You can use the append method. Like (URLSearchParams):
const params = new URLSearchParams()
params.append("type","test")
params.append("type","test2")
Then, you set the params in the request just like you did:
export const getMessages = () =>
baseApi
.get('/message', {
params: {
params
},
paramsSerializer: (params) => {
return qs.stringify(params)
}
})
.then((r) => r.data as Message[])
Related
I have a page [categories][price].js and im trying to achieve the data structure in getStaticPaths e.g
cat1/10 cat1/20 cat1/30 cat1/40 cat2/10 cat/20 etc
I have looked at this post: Next.js: getStaticPaths for nested dynamic routes as it's the same error but as their data structure is a bit different I'm not sure how to translate this to my example
It looks like im mapping the data incorrectly as I get the following error when trying to create my dynamic routes.
Error: Additional keys were returned from `getStaticPaths` in page "/[catSlug]/[price]". URL Parameters intended for this dynamic route must be nested under the `params` key, i.e.:
return { params: { catSlug: ..., price: ... } }
Keys that need to be moved: 0, 1.
How can I correctly map my data?
[categories][price].js
export async function getStaticPaths() {
const prices = [' 10', ' 20', ' 30']
const categories = [{ name: 'cat' }, { name: 'cat2' }, { name: 'cat3' }]
const paths = categories.map(({ slug }) =>
prices.map((price) => ({ params: { catSlug: slug, price: price } }))
)
return {
paths,
fallback: false
}
}
flatten the array
const paths = categories.map(({ name }) =>
prices.map((price) => ({ params: { catSlug: name, price: price } }))
).flat()
I am beating my head against a wall. I have updated to Apollo 3, and cannot figure out how to migrate an updateQuery to a typePolicy. I am doing basic continuation based pagination, and this is how I used to merged the results of fetchMore:
await fetchMore({
query: MessagesByThreadIDQuery,
variables: {
threadId: threadId,
limit: Configuration.MessagePageSize,
continuation: token
},
updateQuery: (prev, curr) => {
// Extract our updated message page.
const last = prev.messagesByThreadId.messages ?? []
const next = curr.fetchMoreResult?.messagesByThreadId.messages ?? []
return {
messagesByThreadId: {
__typename: 'MessagesContinuation',
messages: [...last, ...next],
continuation: curr.fetchMoreResult?.messagesByThreadId.continuation
}
}
}
I have made an attempt to write the merge typePolicy myself, but it just continually loads and throws errors about duplicate identifiers in the Apollo cache. Here is what my typePolicy looks like for my query.
typePolicies: {
Query: {
fields: {
messagesByThreadId: {
keyArgs: false,
merge: (existing, incoming, args): IMessagesContinuation => {
const typedExisting: IMessagesContinuation | undefined = existing
const typedIncoming: IMessagesContinuation | undefined = incoming
const existingMessages = (typedExisting?.messages ?? [])
const incomingMessages = (typedIncoming?.messages ?? [])
const result = existing ? {
__typename: 'MessageContinuation',
messages: [...existingMessages, ...incomingMessages],
continuation: typedIncoming?.continuation
} : incoming
return result
}
}
}
}
}
So I was able to solve my use-case. It seems way harder than it really needs to be. I essentially have to attempt to locate existing items matching the incoming and overwrite them, as well as add any new items that don't yet exist in the cache.
I also have to only apply this logic if a continuation token was provided, because if it's null or undefined, I should just use the incoming value because that indicates that we are doing an initial load.
My document is shaped like this:
{
"items": [{ id: string, ...others }],
"continuation": "some_token_value"
}
I created a generic type policy that I can use for all my documents that have a similar shape. It allows me to specify the name of the items property, what the key args are that I want to cache on, and the name of the graphql type.
export function ContinuationPolicy(keyArgs: Array<string>, itemPropertyKey: string, typeName: string) {
return {
keyArgs,
merge(existing: any, incoming: any, args: any) {
if (!!existing && !!args.args?.continuation) {
const existingItems = (existing ? existing[itemPropertyKey] : [])
const incomingItems = (incoming ? incoming[itemPropertyKey] : [])
let items: Array<any> = [...existingItems]
for (let i = 0; i < incomingItems.length; i++) {
const current = incomingItems[i] as any
const found = items.findIndex(m => m.__ref === current.__ref)
if (found > -1) {
items[found] === current
} else {
items = [...items, current]
}
}
// This new data is a continuation of the last data.
return {
__typename: typeName,
[itemPropertyKey]: items,
continuation: incoming.continuation
}
} else {
// When we have no existing data in the cache, we'll just use the incoming data.
return incoming
}
}
}
}
Expected query string:
http://fqdn/page?categoryID=1&categoryID=2
Axios get request:
fetchNumbers () {
return axios.get(globalConfig.CATS_URL, {
params: {
...(this.category ? { categoryId: this.category } : {})
}
})
.then((resp) => {
// console.log(resp)
})
.catch((err) => {
console.log(err)
})
}
As you can see, it works perfectly with just 1 value for 1 parameter, but if i wanted to make multiple values - it doesn't work, i've tried to use an array:
...(this.category ? { categoryId: [1, 2] } : {})
But it returns this way:
http://fqdn/page?categoryID[]=1&categoryID[]=2
So it just not working. Had a look at this issue: Passing an object with a parameter with multiple values as a query string in a GET using axios
But can't figure out, how he solved this problem.
You can use Axios's paramsSerializer to customize the serialization of parameters in the request.
Note that URLSearchParams serializes array data the way you're expecting:
const searchParams = new URLSearchParams();
searchParams.append('foo', 1);
searchParams.append('foo', 2);
console.log(searchParams.toString()); // foo=1&foo=2
So you could use that class in paramsSerializer as follows:
// my-axios.js
export default axios.create({
paramsSerializer(params) {
const searchParams = new URLSearchParams();
for (const key of Object.keys(params)) {
const param = params[key];
if (Array.isArray(param)) {
for (const p of param) {
searchParams.append(key, p);
}
} else {
searchParams.append(key, param);
}
}
return searchParams.toString();
}
});
// Foo.vue
import axios from './my-axios.js';
export default {
methods: {
async send() {
const { data } = await axios({
url: '//httpbin.org/get',
params: {
categoryId: [1, 2, 3]
}
});
// ...
}
}
}
demo
This is not an axios related issue. It depends on whether your backend service is able to understand query params in this fashion(seems to be framework dependent). From your question, I think it is not working when queryParams like following are sent
?categoryID[]=1&categoryID[]=2
and it expects
?categoryID = 1,2
What you can do is transform array to such string before passing it to params in axios. Update the following piece in your code and it should solve your problem.
...(this.category ? { categoryId: this.category.join(',') } : {})
Take a look at following thread
How to pass an array within a query string?
I'm learning ES6 syntax, as well as the latest Apollo libs. This withData code is adapted from the Githunt-React Apollo demo.
const withData = graphql(GETIMS_QUERY, {
options: ({ params }) => ({
variables: {
"fromID": Meteor.userId(),
"toID": `${params.toID}`,
},
}),
});
params doesn't seem to contain what I expect. I'd like to insert a breakpoint in order to examine the contents of params. But if I add a breakpoint next to options, I find that params is undefined.
I guess I may need to add a breakpoint inside this code block in order to see the contents of params:
const withData = graphql(GETIMS_QUERY, {
options: ({ params }) => ({
//IS THERE A WAY TO ADD A BREAKPOINT IN HERE SOMEHOW?
//MAYBE RETURN `VARIABLES` AS A FUNCTION RESULT?
variables: {
"fromID": Meteor.userId(),
"toID": `${params.toID}`,
},
}),
});
Is there a way to do that?
Thanks in advance to all for any info.
You can call console.log (and you can add a breakpoint on that line) and return the object explicitly:
const withData = graphql(GETIMS_QUERY, {
options: ({ params }) => {
console.log(params);
return {
variables: {
"fromID": Meteor.userId(),
"toID": `${params.toID}`,
},
};
},
});
So the backend (not under my control) requires a query string like this:
http://example.com/?foo=5&foo=2&foo=11
But axios uses a JS object to send the request params:
axios.get('http://example.com/', { foo: 5 });
And obviously an object can't have multiple fields with the same key.
How can I send a request with multiple fields with the same key?
From the Request Config section of the axios documentation:
// `params` are the URL parameters to be sent with the request
// Must be a plain object or a URLSearchParams object
params: {
ID: 12345
},
To use this in a request, you would do
const request = {
params: {
foo: [5, 2, 11]
}
}
axios.get('http://example.com/', request);
The issue with using a plain object is that array parameters are added as
http://example.com/?foo[]=5&foo[]=2&foo[]=11
To make a request to a URL without the [], use URLSearchParams
var params = new URLSearchParams();
params.append("foo", 5);
params.append("foo", 2);
params.append("foo", 11);
var request = {
params: params
};
axios.get('http://example.com/', request);
This will result in a request to
http://example.com/?foo=5&foo=2&foo=11
In Axios request config, you can override params serialization and then use QS NPM module to serialize array with repeat mode
let params = { foo: [5, 2] }
axios.get('path/to/api/',{params}) // URL : https://path/to/api?foo[]=5&foo[]=2
let myAxios = axios.create({
paramsSerializer: params => Qs.stringify(params, {arrayFormat: 'repeat'})
})
myAxios.get('path/to/api/',{params}) // URL : https://path/to/api?foo=5&foo=2
The correct answer with the latest axios version (in the year 2022) would be to set indexes: null in order to get arrayFormat: 'repeat'.
More info: https://github.com/axios/axios/issues/5058#issuecomment-1272107602
Example:
const {data} = await axios.get('https://postman-echo.com/get', {
params: {
a: ['b', 'c', 'd']
},
paramsSerializer: {
indexes: null // by default: false
}
});
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }) ==> config.paramsSerializer.indexes = true // 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }) ==> config.paramsSerializer.indexes = false// 'a[]=b&a[]=c' // Default
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }) ==> config.paramsSerializer.indexes = null// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' }) ==> not supported // 'a=b,c'
Adding more details to #nhydock accepted answer.
When you do
var request = {foo: [5, 2, 11] }
axios.get('http://example.com/', request);
For django application you can receive these as
self.request.query_params.getlist('foo')
also.
Update: Axios already supports this out of the box as of axios#1.1.2
https://github.com/axios/axios/issues/5058#issuecomment-1272107602
#RNR1 Axios already supports this out of the box. By default, Axios encodes arrays in "bracket" format but supports 3 qs formats except "comma".
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' }) ==> config.paramsSerializer.indexes = true // 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' }) ==> config.paramsSerializer.indexes = false// 'a[]=b&a[]=c' // **Default**
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' }) ==> config.paramsSerializer.indexes = null// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' }) ==> **not supported** // 'a=b,c'
So to encode in arrayFormat: 'repeat' you need to do the following:
const {data} = await axios.get('https://postman-echo.com/get', {
params: {
a: ['b', 'c', 'd']
},
paramsSerializer: {
indexes: null // by default: false
}
});
Echo response:
{
args: { a: [ 'b', 'c', 'd' ] },
headers: {
'x-forwarded-proto': 'https',
'x-forwarded-port': '443',
host: 'postman-echo.com',
'x-amzn-trace-id': 'Root=1-63409c06-5d9fc0344ceaf9715466e0e3',
accept: 'application/json, text/plain, */*',
'user-agent': 'axios/1.1.0',
'accept-encoding': 'gzip, deflate, br'
},
url: 'https://postman-echo.com/get?a=b&a=c&a=d'
}
Disclaimer: I have never used Axios and couldn't find any reference in
the documentation. It is still worth a try. Personally, I would
implement it this way in a library.
It might also be possible to pass arrays as values for the parameter:
axios.get('http://example.com/', { foo: [1, 2, 3, 4, 5] });
If one uses the ready URLSearchParams the handling of multiple parameter values with the same name works with axios as well ... I guess the support for IE came in 2017 ... Works on Safari too, although the links claims that it might not ..
function getUrlParams(){
// handles multiple param values with the same name
var url_params = new URLSearchParams();
if( window.location.toString().indexOf("?") != -1) {
window.location.search.split('?')[1].split('#')[0]
.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) {
var attr = decodeURIComponent(key)
var val = decodeURIComponent(value)
url_params.append(attr,val);
});
} else {
// create a default set of params - you might not need this one ...
url_params = { some_param:"some_value" };
}
return url_params ;
}
function getBackEndData(url_params, back_end_url){
// the replace is just a fancy way of converting front-end to back-end call
return axios.get( back_end_url , { params: url_params } )
.then(response => {
return response.data ;
})
.catch(function(error) {
return error.response ;
console.log(error.response.data);
})
}