Vue v-for with v-if - vue.js

I found some inconsistency in the Vue documentation. If someone clarify this please. Looking at v-for-with-v-if it says it could be useful to do it. Which in my case I am in that exact situation. But now eslint is complaining.
So I looked at the style guide and its telling me to avoid this. So there is some clear contradiction.
Question: Is it really that bad that you should avoid it?
My Opinion: I don't see it as bad. I have quite a few use cases where this is useful.

Way 1:
all nodes will be rendered on every items[] change
<span v-for="item in items" v-if="item.shouldRender">...</span>
Way 2:
all nodes will be rendered once
<template v-for="item in items">
<span v-if="item.shouldRender">...</span>
</template>
Way 3:
only filtered nodes will be rendered. Filtered list is cached.
<span v-for="item in computedShouldRenderItems">...</span>
I think that 'way-1' is not REALLY bad. But I prefer to avoid it.

Related

Render directive conditionally

I've been struggling all day to find a way to conditionally render a directive on an element.
I ended up on this page: https://vuejs.org/guide/extras/render-function.html but then I wasn't able to append my compiled template (using vue-template-compiler, since I'm using some version of vue which doesn't include the compiler).
At the end of the day this is what I figured:
<div>Some foo</div>
<template v-if="withDirective">
<input :value="value"
:disabled="disabled"
:type="type"
v-some-directive="someValue" />
</template>
<template v-else>
<input :value="value"
:disabled="disabled"
:type="type" />
</template>
<div>Some bar</div>
Is there a better way?
I have a lot more attributes on the input, so there's really a lot of duplicate code which I would love to avoid.
This html is inside a custom component, so all the values like disabled, required, etc, are props passed from outside.
The best way I found so far is compiling a string template conditionally using compileToFunctions.
So instead of having the template tags and the v-if in the template, I simply add the conditional directive(s) before compiling the component.
ATTENTION
Keep in mind that the compilation is runtime, I don't have enough experience with Vue (yet) to tell if this way is faster than using v-if. What is sure is that we saved a lot of duplicate lines in the template, which is handy when editing common parts.

Why are Vue dynamic components destroyed inside loop every re-render?

I run into an issue with Vuejs 2.x version (latest). While rendering a list of item inside a loop, if I make changes to the items then the normal components are not destroyed but the dynamic components will always be destroyed:
I have put a short sample code here:
https://gist.github.com/yellow1912/fc1c053e07c1ca136148484cf7f79d1a
I have also put a codepen here:
https://codepen.io/raineng/pen/zYGOXYY?editors=1111
<nl-test inline-template>
<div>
<div v-on:click="increase"> increase here please </div><br><br>
<div v-on:click="decrease"> decrease here please </div>
<ul>
<li v-for="(value, key) in getItems()" :key="key">
printing
<component :is="getItem()" :key="key"></component>
<nl-test inline-template>
<div>
this is a test here
</div>
</nl-test>
</li>
</ul>
</div>
</nl-test>
To see what I mean, open the console tab on codepen, click the add item and you will see that the dynamic component items are destroyed and re-created everytime.
I found out why, I need to use keep-alive:
https://v2.vuejs.org/v2/guide/components-dynamic-async.html
To quote:
When switching between these components though, you’ll sometimes want
to maintain their state or avoid re-rendering for performance reasons
Recreating dynamic components is normally useful behavior, but in this
case, we’d really like those tab component instances to be cached once
they’re created for the first time. To solve this problem, we can wrap
our dynamic component with a element
Wasted 2 days on this issue and then I found the answer just a moment after posting this on StackOverflow. Hope it helps someone.

Algolia Instantsearch (Vuejs) - Place searchbox outside (in a different component)

I'm building a Single Page Application using, Vue, Vue-router and Vuex. I've tried to implement Algolia Instantsearch vuejs, but I'm having some issues. Since my app is using a lot of nested components, I'm having a hard time figuring out how to structure this one.
This is the basic structure:
- App
- Header (this is where the search input is placed)
- Content
- Search (this is where the refinements and results are shown)
- Footer
I've read the documentation, but I might have missed something. Let's say the user is on the homepage, when starting typing into the searchinput in the Header component. I then use vue-router to go to /search, but this doesn't seem to work?
I don't know how to do this? As from what I can understand, the documentation only show you how to sync with vue-router and now how to handle my scenario.
I believe this is a fairly common use case for instantsearch, but I couldn't find anything searching through Google. Maybe because, I don't know how to describe the issue.
I hope you can help.
Thanks!
If you use the latest version of vue-instantsearch, you may use ais-configureas describe by https://www.algolia.com/doc/api-reference/widgets/configure/vue/.
<ais-instant-search :index-name="indexName" :search-client="searchClient">
<ais-configure :query="query" />
<ais-hits>
<div slot="item" slot-scope="{ item }">
<h2>{{ item }}</h2>
</div>
</ais-hits>
</ais-instant-search>

Vuejs computed properties and jquery ui sortable issue

In my project I have a component with three ul lists. Also I have some kind of data array with items, each item has some properties. My aim is to:
Distribute items in basic array into three lists
Make it possible to drag&drop items between lists and accordingly update items data, since each item has a property which tells us which list the item belongs
Instead of copy-pasting a lot of code, I tried to reproduce the incorrect behaviour in jsfiddle by using simple example here:
https://jsfiddle.net/89pL26d2/4/
The thing is, when you drag&drop, you got exactly 2 items dragged, instead of one.
However, when I switched from computed properties to watch, I got the desired behaviour and everything worked just fine.
I figure out which line causes the error: the line when I update item property telling me which list the item should belong to after drag is finished. But I can't figure out why it causes this
I know that it's not the best way to work with HTML directly, but I'm okay with that for now.
Generally, whenever I see an issue in Vue, especially related to lists, where the wrong item is updated or something like that, it comes down to Vue trying to be smart but getting it wrong because it doesn't have the best information. This is almost always solved by using a key.
It is recommended to provide a key with v-for whenever possible,
unless the iterated DOM content is simple, or you are intentionally
relying on the default behavior for performance gains.
Key.
<div id="app">
<div>
<ul id="listA" data-list="A" class="connectedSortable">
<li v-for="item in listAFiltered" :key="item.id" :data-id="item.id">{{ item.title }}</li>
</ul>
<ul id="listB" data-list="B" class="connectedSortable">
<li v-for="item in listBFiltered" :key="item.id" :data-id="item.id">{{ item.title }}</li>
</ul>
</div>
</div>
Adding a key fixes your issue.

Oddity with templates and root node with vue.js

I think this is possibly a bug I've stumbled across, not sure. I'm getting this Vue.js warning for a component:
vue.js:2611 [Vue warn]: Cannot use <template> as component root element because it may contain multiple nodes:
The problem seems to be this:
<template id="tpl-field">
<template v-if="fieldType==='checkbox-inline'">
<label class="checkbox-inline">[SNIP]</label>
</template>
<template v-else>
[SNIP]
</template>
</template>
So I have two template nodes, which seems to be the multiple nodes it's choking on (certainly each of the template nodes contains just a single node). Yet this is an if-else in Vue - if one of the nodes is present, the other logically cannot be.
Demo of the problem here: https://jsfiddle.net/jonmor51/Ldz3k0jp/1/. If I wrap everything in a div, it works. But without, it fails. (Unfortunately, in the context where I want to use this, namely for inline checkboxes within a Bootstrap grid, wrapping in a div breaks things).
Not sure if this will solve your problem with bootstrap... but you could wrap you inner templates with a <transition> tag and set a key to each one.
Please check this working fiddle
https://jsfiddle.net/AldoRomo88/7c7znu3p/
helpful thing - just use div display: contents as a root of the component and browser will ignore that element and consider child elements (which can be many) as children of upper dom element
<div style="display: contents">
<template v-if="...">
<template v-for="..."> ...
</template>
<template v-if="...">
</template>
</div
works even inside tables!
The inner templates direct children, are they single elements? If so, you can just remove the inner templates and move v-if to the label.
Or, just use span rather than div as your quick fix, which won't break inline elements' style.