Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support more collection data types in v-for #2410

Closed
wenLiangcan opened this issue Feb 28, 2016 · 47 comments
Closed

Support more collection data types in v-for #2410

wenLiangcan opened this issue Feb 28, 2016 · 47 comments

Comments

@wenLiangcan
Copy link

At some situations, plain object isn't the best choise. I tried to render a Map object by v-for, but seems Vue does not support it currently. (Here's a post I created in the Help thread on the forum.)

Hope Vue can provide the for ... of syntax in v-for to iterate over data types like Map and Set.

For example:

const map = new Map();
map.set('key1', 'val1');
map.set('key2', 'val2');

and we can render map in this way:

<ul>
    <li v-for="[key, val] of map">{{key}} - {{val}}</li>
</ul>
@yyx990803 yyx990803 changed the title [Feature Request] Support more collection data types in v-for Support more collection data types in v-for Feb 28, 2016
@FadyMak FadyMak self-assigned this Mar 22, 2016
@nervgh
Copy link

nervgh commented Apr 28, 2016

A duplicate of #1319

@wenLiangcan , you could use something like this:

<ul>
    <li v-for="[key, val] of get(map)">{{key}} - {{val}}</li>
</ul>

where get() is your function.

@agalazis
Copy link

haha! similar issues open all the time and and people insist that they cannot justify implementation well people want to use it that's one justification I can also list the bazillion of issues closed that ask the same thing xD. I also found one that justifies the use-case really well and refers to es6 specification when it comes to map order ->still closed.

@posva
Copy link
Member

posva commented Aug 25, 2016

People wanting to use a feature is not, only by itself, an argument that can justify the need of having such feature, it's necessary to weigh the cost and benefits (what problem is being solved) of adding such feature

@agalazis
Copy link

Yep but still the arguments that people use to close the issue are still not valid or at least not valid for all usecases eg the example of the elipen who justified his usecases very well as I mentioned above

@LinusBorg
Copy link
Member

LinusBorg commented Aug 25, 2016

If you want to discuss a specific issue, link to it please.

Plus, this Feature issue is open. It does not make sense to have more than one issue open for the same request.

@catsclaw
Copy link

catsclaw commented Sep 7, 2016

It's important to be able to iterate over iterators in loops. That seems plainly obvious. It's a fundamental feature of the language.

The reasons for supporting it are:

  1. Iterators, Maps, and Sets are all valid ES6. Refusing to support them means limiting yourself to ES5, which is a decision becoming less and less justified over time.
  2. I'm building an application that has internal data stored in Maps and Sets. Instead of making them available to the UI, I now need to keep the data synced between the two manually, or write boilerplate and import it into my templates to do the conversion whenever the data is needed. This is exactly what Vue is intended to avoid.

@inca
Copy link

inca commented Dec 21, 2016

Since #1319 is closed, it's worth reiterating on current decision here. In short, the feature is not trivial to implement (on observation mechanism level), so it's not about justifying use cases, it's about the amount of work and trade-offs.

I would really appreciate this feature, too. On the other hand, if observing ES6 data types becomes terribly hacky or, for example, compromise performance or other qualities, then people who do not currently use Maps and Sets with Vue might not appreciate this change.

@lmj0011
Copy link

lmj0011 commented Jun 9, 2017

I suppose using Array.from() inside a computed function will have to be your best friend for now. 😞

@alexsandro-xpt
Copy link

Any solution for that?

@nickmessing
Copy link
Member

Small update, this will come if/when Vue decides to drop "legacy" browsers and will move to Evergreen ones with Proxy instead of set/get for reactivity.

@alexsandro-xpt, just use a computed function that returns Array.from(yourDataSet).

@alexsandro-xpt
Copy link

alexsandro-xpt commented Jul 27, 2017

@nickmessing I try with Map, doesn't work.
The computed array length value is always 0.

@inca
Copy link

inca commented Jul 27, 2017

Just Array.from is probably not solution you want because of lack of reactivity (changes to yourDataSet will not get propagated to Vue).

As mentioned earlier, Sets and Maps are not observable by Vue. In order to use those — either in v-for, or in computed properties, methods, watchers, template expressions, etc. — you need to create a serializable replica of this structure and expose it to Vue. Here's a naive example which uses a simple counter for providing Vue with information that Set is updated:

data() {
  mySetChangeTracker: 1,
  mySet: new Set(),
},
  
computed: {
  mySetAsList() { 
    // By using `mySetChangeTracker` we tell Vue that this property depends on it,
    // so it gets re-evaluated whenever `mySetChangeTracker` changes
    return this.mySetChangeTracker && Array.from(this.mySet);
  },
},

methods: {
  add(item) {
    this.mySet.add(item);
    // Trigger Vue updates
    this.mySetChangeTracker += 1;
  }
}

This illustrates a kinda hacky but 100% working method for making non-observable data reactive. Still, in real world cases I ended up with serialized versions of Sets/Maps (e.g. you'd probably want to store the modified versions of sets/maps in localstorage and thus serialize them anyway), so no artificial counters/hacks were involved.

I personally think this is a fair solution to a problem, but it definitely deserves some official documentation — otherwise it's impossible to justify this as non-hacky way of dealing with Vue internals.

@nickmessing
Copy link
Member

nickmessing commented Jul 27, 2017

@alexsandro-xpt, sorry, I was wrong, computed will be hacky as @inca said, another hacky solution would be using $forceUpdate with a method, here's an example fiddle

@alexsandro-xpt
Copy link

Thank you @nickmessing and @inca, this work fine with my new Map()!!

@dsandber
Copy link

Right now when you do a "v-for" over a "Map", the v-for acts as if it had received an empty array.

Regardless of the outcome of the extended discussion about whether/how to support Maps and Sets, it would save a lot of people a lot of debugging time if Vue simply warned "Maps and Sets are not yet supported -- see #2410".

@warent
Copy link

warent commented Sep 5, 2017

Yup, Google search for this feature brought me to this ticket (after a few annoying mixups with Vue.set)

👍 This should be in the v-for documentation!

@alexsandro-xpt
Copy link

alexsandro-xpt commented Sep 5, 2017

Truly, should be in v-for documentation!

@yyx990803
Copy link
Member

/cc @chrisvfritz let's try to add a note about support for these types up in the docs for v-for (both API and the list rendering section) - I'll also take a look at them in 2.5.

@chrisvfritz
Copy link
Contributor

@yyx990803 I wonder if a console warning would be better for this, since that would tell people what's wrong immediately, obviating the need to search for the solution.

We're also already very explicit in the docs about which types we do support, Map and Set not being among them. I can definitely see the argument for why one might hope all iterables would work with v-for, but I don't think we currently give readers any reason to expect they would.

@schmod
Copy link

schmod commented Sep 11, 2017

I'm not quite seeing the argument against adding support for Set.

Set itself can be cleanly polyfilled, and unless I'm missing something, it seems like Vue's approach for adding reactivity to arrays could very easily be extended to sets. We'd only need to wrap .add(), .clear(), and .delete().

@inca
Copy link

inca commented Sep 12, 2017

My best guess (please correct/sorry if I'm wrong): the trickiest part is to wrap a Set constructor, which accepts an iterable. I don't see how iterable can be made observable, because in its general form it's just a function (i.e. next) with no referenceable state (think of generator-based iterator as an example).

@lloydjatkinson
Copy link

Any future plans to add support? Is there a technical reason Vue could not support Map and Set?

@steappe
Copy link

steappe commented Feb 10, 2018

The current problem with the Vue.set method on a plain object is that it triggers way too many subscribers when a property is added to the object. Actually, all the subscribers of all the properties are triggered when only one property is added.

The performance of the view is badly impacted when a map like collection contains hundredth of keys. For example, in my project thousands of subscribers are triggered when one element is added to the map using the Vue.set operation:

Vue.set(state.items, itemId, item); // where items is a plain object.

When I look deeply into the Vue.js code, I can see where the problem comes from. The dependencies that are triggered are those of the object, which means that if the object has one property for each key, then all the dependencies of all the keys are triggered when just adding one key.

So using plain objects to mimic a map does not look as the right solution, and therefore having support for a map in vue is more than welcomed for large collections of items.

@cawa-93
Copy link

cawa-93 commented Mar 16, 2018

Is there any news about future plans and possibly about native Map/Set support?

@tomeightyeight
Copy link

This article details upcoming support in 2.6 - but there's nothing about that in the official roadmap from what I can see?

https://medium.com/@alberto.park/the-status-of-javascript-libraries-frameworks-2018-beyond-3a5a7cae7513

"The current latest of the core is 2.5.x. Next minor release(v2.6), will support native ESM import, improved async error handling, iterator on ‘v-for’ directive and more."

Not sure where they got that information from?

nd9600 added a commit to nd9600/flowyClone that referenced this issue May 30, 2018
@blackst0ne
Copy link

Found this issue while was debugging the heck Vue's behavior over Set data objects. 🤔

@OCJvanDijk
Copy link

OCJvanDijk commented Aug 7, 2018

For people like me that were wondering about the roadmap for this, Evan You says in this video that Map and Set support is "likely" to arrive in 2.6, but that was in May, so that's all I know.

@bradisbell
Copy link

@yyx990803 It's unfortunate that this issue in the tracker is marked as closed, especially if you're considering adding support in the near future. Where can we follow progress on this feature? Is there another issue somewhere?

@steven87vt
Copy link

Just wondering for the sake of argument, and maybe I am doing it wrong, but since you can track array mutation using the Mutation Methods cant you just track an array of an object and be logically complete? you dont get all the same features implemented in Map but the ones you want would be pretty easily addressed especially if you are using something like _ or lodash.

@pkanane
Copy link

pkanane commented Sep 5, 2018

Its sad but until the team adds this we might have to use alternate data structures

@Dwood15
Copy link

Dwood15 commented Oct 31, 2018

Just want to chime in that we were going to use a Map for our data structure, then decided against it because of a lack of first-party support.

@bodograumann
Copy link

This is planned for vue.js v3.0.
Compare: https://medium.com/the-vue-point/plans-for-the-next-iteration-of-vue-js-777ffea6fabf

@softwareCobbler
Copy link

softwareCobbler commented Sep 23, 2020

is this supported now in Vue3 (that is, reactive Map and Set)?

@AhmedEzzat12
Copy link

any updates ?

@bodograumann
Copy link

I can confirm that Set.add is reactive in Vue 3.

@MattiasMartens
Copy link

I've tested Maps for reactivity as well and they work. In particular (for my use case), calls to computed functions that use map.get() update as expected as long as (of course) the Map's initialization is wrapped with reactive().

I'll add that I love Maps and use them a lot. They are much more efficient than Object-based dictionaries, especially when working with integer keys. Plus they allow indexing on object references, which can't be done with Objects. I like to use them for managing aggregated data that is useful in various places in the app.

Very happy to see that this is implemented!

@andrewvasilchuk
Copy link

Using @vue/composition-api I have come up with the following solution for Vue 2.

// useReactiveSet.ts
import { ref, computed } from '@vue/composition-api'

export default function useReactiveSet<T>() {
  const version = ref(1)
  class ReactiveSet extends Set<T> {
    add(value: T) {
      super.add(value)
      version.value += 1
      return this
    }
  }
  const inner = ref(new ReactiveSet())

  const set = computed(() => {
    version.value
    return inner.value
  })

  return set
}
// Component.vue
{
  // ...
  setup() {
    const set = useReactiveSet<number>()

    setTimeout(() => {
      // this will trigger rerender
      set.value.add(2)
    }, 1000)

    return {
      set,
    }
  },
  // ...
}

You can use the same approach for Map.

@andrewvasilchuk
Copy link

@inca I have created a package that was adopted from your comment. Thank you so much! https://github.com/bluecanvas/vue-reactive-collection

@Hansomeble
Copy link

Hansomeble commented May 8, 2022 via email

@wpplumber
Copy link

It works by assigning new clone with property changes.

state.files = new Map(allFiles);

@Hansomeble
Copy link

Hansomeble commented Sep 21, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests