0
votes

Hiiiii everyone,

have a nice day,

I have a project that syncs the URL with selection on the component.

I pass the query object to the VueRouter.

The VueRouter stringify that object and use it as an URL

After that, I can get that Object through this.$route.query object (Vuerouter will parse that URL to normal Object)

Here is the minimal version on CodeSandbox

enter image description here

I pass these objects to Vue router: { destinations: ['Hanoi'] } { destinations: ['Hanoi','Da Nang'] } { destinations: ['Ho Chi Minh City'], travelStyle:['Discovery','Adventure'] }

App.vue

<template>
  <div id="app">
    <div class="tour">
      <h2>Which tour?</h2>
      <router-link :to="{ path: 'tours', query: { destinations: ['Hanoi'] } }">Hanoi</router-link>
      <br>
      <router-link
        :to="{ path: 'tours', query: { destinations: ['Hanoi','Da Nang'] } }"
      >Hanoi - Da Nang</router-link>
      <br>
      <router-link
        :to="{ path: 'tours', query: { destinations: ['Ho Chi Minh City'] } }"
      >Ho Chi Minh City</router-link>
      <br>
      <br>
      <span>Route Query: {{this.$route.query}}</span>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

The problem is, if I using default parseQuery/StringifyQuery of VueRouter, the URL quite of long

tours?destinations=Ho%20Chi%20Minh%20City&travelStyle=Discovery&travelStyle=Adventure

for this object: { destinations: ['Ho Chi Minh City'], travelStyle:['Discovery','Adventure'] }

I followed the instruction in this topic Custom querystring parser and add qs as a custom querystring parser.

And using an external package allow me to control the URL as what I want,

Here is inside the router file - full running project in codesandbox

// ...

const router = new VueRouter({
    mode: "history",
    base: "/",
    routes,

    stringifyQuery: (query) => {
        qs.stringify(query, {
            encode: false,
            indices: false,
            arrayFormat: "comma",
            addQueryPrefix: true,
        });
    },

    parseQuery: (query) => {
        console.log("queryString", query);
        const a = qs.parse(query, {
            comma: true,
        });
        console.log("after parse:", a);
        return a;
    },

});

Now the URL is quite shorter when using commas in array.

tours?destinations=Hanoi tours?destinations=Hanoi,Da%20Nang tours?destinations=Ho%20Chi%20Minh%20City&travelStyle=Discovery,Adventure

But another problem coming up: Stringify with comma format should add square brackets to parameter name to identity array with single value

Because using a comma to separate each element inside the array, so we cannot know that destinations=Hanoi means { "destinations": [ "Hanoi" ] } or { "destinations": "Hanoi" }

**When I click to routerlink to the URL tours?destinations=Hanoi I got { "destinations": [ "Hanoi" ] } (correct)

enter image description here

but if I reload that very URL I got { "destinations": "Hanoi" } (wrong)**

enter image description here

From that issues, they said we can add brackets on the end of the key on an array with a single value to get what we want.

des[]=hanoi > { des: [ 'hanoi' ] }

des=hanoi,hai phong > { des: [ 'hanoi', 'hai phong' ] }

here is my test file on repl.it


const  qs = require('qs');


const stringifyQuery = (query) => {
   Object.keys(query).forEach((item) => {

      if(Array.isArray(query[item])&&query[item].length===1) {

          query[item+'[]'] = query[item]; 
          delete query[item];
      }
  });


  return qs.stringify(query, {
      encode: false,
      indices: false,
      arrayFormat: 'comma',

  });
};

let parseQuery = (query) =>
    qs.parse(query, {
      comma: true,
    });


const a = {des: ['hanoi']};

const b = {des: ['hanoi','hai phong']};


console.log(stringifyQuery(a)); // des[]=hanoi
console.log(stringifyQuery(b)); // des=hanoi,hai phong
console.log(parseQuery(stringifyQuery(a))); // { des: [ 'hanoi' ] }
console.log(parseQuery(stringifyQuery(b))); // { des: [ 'hanoi', 'hai phong' ] }

It's working fine!

But when I use it on vue-router, it not.

Once again, it's showing different results between click to the router-link and reload the same URL!

For this URL tours?destinations[]=Hanoi

If I click through router-link, it still got { "destinations[]": [ "Hanoi" ] } instead "destinations": [ "Hanoi" ]

enter image description here

But if I reload the page, I got the correct one: { "destinations": [ "Hanoi" ] }

enter image description here

I tried to figure out what happened, but it's quite touching for me as a newbie,

Does anyone have any idea or keyword in this problem?

Thanks a lot when you take your valuable time and read my long question,

It means a lot to me, I really appreciate it!

1

1 Answers

0
votes

I got the answer,

In my custom StringifyQuery, I mutate the query Object to add square brackets to it key (destinations[]: Hanoi)

And then after that, the router still use that object and show in the next route

That why, when I console.log this.$route.query it shows the edited query (destinations[]: Hanoi)

To fix that, I have to avoid mutating the query object, and that's all

full code in CodeSandbox

stringifyQuery: (query) => {
        const queryClone = { ...query };

        Object.keys(queryClone).forEach((item) => {
            if (
                Array.isArray(queryClone[item]) &&
                queryClone[item].length === 1 &&
                !item.includes("[]")
            ) {
                queryClone[`${item}[]`] = queryClone[item];
                delete queryClone[item];
            }
        });

        return qs.stringify(queryClone, {
            encode: false,
            indices: false,
            arrayFormat: "comma",
            addQueryPrefix: true,
        });
    },

My lesson: Never mess with an outside object, we have to clone it (deep or shadow base on context) and edit it in the clone version