tl;dr; this function:
export const filterToQueryString = (filterObj, overrides) => {
const copy = Object.assign(overrides || {}, filterObj)
const searchParams = new URLSearchParams()
Object.entries(copy).forEach(([key, value]) => {
if (Array.isArray(value) && value.length) {
value.forEach(v => searchParams.append(key, v))
} else if (value) {
searchParams.set(key, value)
}
})
searchParams.sort()
return searchParams.toString()
}
I have a React project that used to use query-string
to serialize and deserialize objects between React state and URL query strings. Yesterday version 6.0.0 came out and now I'm getting this error during yarn run build
:
yarn run v1.5.1 $ react-scripts build Creating an optimized production build... Failed to compile. Failed to minify the code from this file: ./node_modules/query-string/index.js:8 Read more here: http://bit.ly/2tRViJ9 error An unexpected error occurred: "Command failed. Exit code: 1
Perhaps this is the wake up call to switch to URLSearchParams (documentation here). Yes it is. Let's do it.
My use case is that I store a dictionary of filters in React this.state
. The filter object is updated by submitting a form that looks like this:
Since the form inputs might be empty strings my filter dictionary in this.state
might look like this:
{
user: '@mozilla.com',
created_at: 'yesterday',
size: '>= 1m, <300G',
uploaded_at: ''
}
What I want that to become is: created_at=yesterday&size=>%3D+1m%2C+<300G&user=%40mozilla.com
So it's important to be able to skip falsy values (empty strings or possibly empty arrays).
Sometimes there are other key-values that needs to be added that isn't part of what the user chose. So it needs to be easy to squeeze in additional key-values. Here's the function:
export const filterToQueryString = (filterObj, overrides) => {
const copy = Object.assign(overrides || {}, filterObj)
const searchParams = new URLSearchParams()
Object.entries(copy).forEach(([key, value]) => {
if (Array.isArray(value) && value.length) {
value.forEach(v => searchParams.append(key, v))
} else if (value) {
searchParams.set(key, value)
}
})
searchParams.sort()
return searchParams.toString()
}
I use it like this:
_fetchUploadsNewCountLoop = () => {
const qs = filterToQueryString(this.state.filter, {
created_at: '>' + this.state.latestUpload
})
const url = '/api/uploads?' + qs
...
fetch(...)
}
UPDATE - May 2018
In the original blog post (now edited and corrected) I copied the wrong code and didn't discover the subtle mistake until now.
What was wrong as the order of the arguments to Object.assign()
.
Wrong
const copy = Object.assign(filterObj, overrides || {})
Correct
const copy = Object.assign(overrides || {}, filterObj)
The old version was dangerous because it mutated the filterObj
passed in. So if you did something like
const qs = filterToQueryString(this.state.filter, {
created_at: '>' + this.state.latestUpload
})
it would potentially mutate this.state.filter
which isn't desirable.
Comments
URLSearchParams has problem with the Internet Explorer