How can i make the react-select createable update the option menu when a new item is added? - react-select

Current code & output:
const [options, setOptions] = useState([
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
]);
<CreatableSelect
isMulti
onChange={handleChange}
options={options}
className="select"
backspaceRemovesValue
onCreateOption={(value) => {
const newOption = { value: value, label:value };
setOptions([...options, newOption]);
}}
/>
Whenever a new option is created, it adds the option to the options list, however, the tag is not appended to the input select alongside the other tags automatically. Below is an image directly after clicking enter:
List updates, but tag not appended
The created tag is in the dropdown list, but not appended to input
Expected output:
The tag appends to the input and is also in the options list.

Related

Vue-i18n and v-select - can't translate

I have a problem to translate the information with vue i18n in my v-select. All others translation work but not this one... And i don't find a solution ...
HTML :
<v-card-text>
<v-select v-model="model" :items="propsList" :items-text="propsList.text" label="Select a reason:" clearable />
</v-card-text>
DATA eg : ($t = i18n)
propsList: [
{ text: this.$t('XXX.A') as string, value: 'X' },
{ text: this.$t('XXX.B') as string, value: 'Y' },
{ text: this.$t('XXX.C') as string, value: 'Z' },
],
Traduction :
{ "en": {
"XXX" : {
"A": "A", ...}},
"fr": {
"XXX" : {
"A": "A", ...}},
In my App.vue :
data ... :
languages: [
{ text: 'English', value: 'en' },
{ text: 'Français', value: 'fr' },
],
watch: {
language(val: string) {
this.setLanguage(val);
this.$i18n.locale = val;
},
},
I'm keep trying ! But thanks by advance :)
There is a prop called :get-option-label that is useful for i18n, that way you can just pass the i18n key in your option array:
<v-select :options="options" :get-option-label="option => $t(option.text)"></v-select>
Then options would look like this:
options = [
{ text: "XXX.A", value: 0 }
{ text: "XXX.B", value: 1 }
{ text: "XXX.C", value: 3 }
]
More details: https://vue-select.org/api/props.html#getoptionlabel
v-select doesn't have a prop named 'items-text' (items with s). You probably mean item-text (without s).
the prop item-text is used to specify the "path" where each item of your data has the text, it defaults to the string "text" which means the text of the item is found at the property "text"
if for example, you have the item with the structure:
{
value: 'some value',
name: 'John'
}
You should pass the string "name".
since you have your data with the text as text property, your template should look like this:
<v-card-text>
<v-select v-model="model" :items="propsList" label="Select a reason:" clearable />
</v-card-text>
Other options for this prop are:
An array of strings
Use this for nested properties, say for example you have your item's structure as follows:
{
value: 'whatever',
data: {
name: {
en: 'John'
}
}
}
you should pass ['data', 'name', 'en'], Vuetify will resolve the name.
A callback function
The callback function you pass will be called for each item and the item itself will be passed as a parameter, you should return whatever string you want to be displayed, this might be useful if you want to concatenate two properties of your items, e.g. first name and last name. or to display a prefix based on some value

Dynamic item template slots within v-data-table with custom components & helpers

Say I have a custom component that uses Vuetify's v-data-table within.
Within this component, there's multiple other custom components such as loaders and specific column-based components for displaying data in a certain way.
I found myself using the same code for filtering, retrieving data, loaders etc. across the project - so not very DRY.
The things that vary are:
API request url to retrieve data from (which I can pass to this generic component)
headers for v-data-table (which I pass to this generic component)
specific item slot templates!
(One file using this same code would need a column modification like the below, requiring different components sometimes too):
<template v-slot:[`item.FullName`]="{ item }">
<router-link class="black--text text-decoration-none" :to="'/users/' + item.Id">
<Avatar :string="item.FullName" />
</router-link>
</template>
Where another would have for example:
<template v-slot:[`item.serial`]="{ item }">
<copy-label :text="item.serial" />
</template>
There are many more unique "column templates" that I use obviously, this is just an example.
modifying items passed to v-data-table in a computed property (to add "actions" or run cleanups and/or modify content before displaying it - not related to actual HTML output, but value itself)
computed: {
items () {
if (!this.data || !this.data.Values) {
return []
}
return this.data.Values.map((item) => {
return {
device: this.$getItemName(item),
serial: item.SerialNumber,
hwVersion: this.$getItemHwVersion(item),
swVersion: this.$getItemSwVersion(item),
actions: [
{ to: '/devices/' + item.Id, text: this.$t('common.open') },
{ to: '/devices/' + item.Id + '/replace', text: this.$t('common.replace') }
],
...item
}
})
}
there are some unique methods that I can use on certain template slot item modifications, such as dateMoreThan24HoursAgo() below:
<template v-slot:[`item.LastLogin`]="{ item }">
<span v-if="dateMoreThan24HoursAgo(item.LastLogin)">{{ item.LastLogin | formatDate }}</span>
<span v-else>
{{ item.LastLogin | formatDateAgo }}
</span>
</template>
I can always make this global or provide them as a prop so this point should not be a big issue.
So my questions are:
What is the best way to use one component with v-data-table within but dynamically pass template slots and also allow item modification prior to passing the array to the v-data-table (as per point 3 and 4 above)
is there a better way to approach this since this seems too complex (should I just keep separate specific files)? It does not feel very DRY, that's why I'm not very fond of the current solution.
Basically I would be happy to have something like:
data: () => {
return {
apiPath: 'devices',
headers: [
{ text: 'Device', align: 'start', value: 'device', sortable: false, class: 'text-none' },
{ text: 'Serial Number', sortable: false, value: 'serial', class: 'text-none' },
{ text: 'Status', value: 'Status', class: 'text-none' },
{ text: 'Calibration', value: 'NextCalibrationDate', class: 'text-none' },
{ text: '', sortable: false, align: 'right', value: 'actions' }
],
itemsModify: (items) => {
return items.map((item) => {
return {
device: this.$getItemName(item),
serial: item.SerialNumber,
actions: [
{ to: '/devices/' + item.Id, text: this.$t('common.open') },
{ to: '/devices/' + item.Id + '/replace', text: this.$t('common.replace') }
],
...item
}
})
},
columnTemplatesPath: '/path/to/vue/file/with/templates'
}
}
And then I'd just call my dynamic component like so:
<GenericTable
:api-path="apiPath"
:headers="headers"
:items-modify="itemsModify"
:column-templates-path="columnTemplatesPath"
/>
Relevant but not exactly a solution to my question:
Is it possible to use dynamic scoped slots to override column values inside <v-data-table>?
Dynamically building a table using vuetifyJS data table

How to remove preselected tag in vue-multiselect

I need a dropdown that supports tagging and can be styled easily, so I decided to implement vue-multiselect. It works but the problem is that I have a predefined tag in my dropdown when the page loads and I don't what that, how can I remove it? Here is how it looks now:
and here is how I want it to look:
Here is my html code:
<div>
<multiselect v-model="value" tag-placeholder="Add this as new tag" placeholder="Assesors" label="name" track-by="code" :options="options" :multiple="true" :taggable="true" #tag="addTag"></multiselect>
</div>
and here is my js:
data () {
return {
showAddUserDialog: false,
value: [
{ name: 'Assesors', code: 'as' }
],
options: [
{ name: 'Assesors', code: 'as' },
{ name: 'Finance', code: 'fi' },
{ name: 'Sales', code: 'sa' }
]
}
},
methods: {
addTag (newTag) {
const tag = {
name: newTag,
code: newTag.substring(0, 2) + Math.floor((Math.random() * 10000000))
}
this.options.push(tag)
this.value.push(tag)
}
}
Well, by the <multiselect> configuration you used and the expected behavior your showed...
You have this https://imgur.com/a/D9nEKfD to become this https://imgur.com/a/baTYXht
I looks like you want to load your page only showing the placeholder, but if you'd like to only show your placeholder, you shouldn't have a value in the value variable, as you have:
data () {
return {
showAddUserDialog: false,
value: [
{ name: 'Assesors', code: 'as' } // <- remove this
],
options: [
{ name: 'Assesors', code: 'as' },
{ name: 'Finance', code: 'fi' },
{ name: 'Sales', code: 'sa' }
]
}
}
Because the behavior of the component is show your current tags, right?
So if this tag is the placeholder, you can add it to the value when the user submits the form without choosing any other tag.
But if you want to have it there as the value and trickery the component to only show it as a placeholder, I suggest you to deep dive in the docs about custom templating...
Here talks about it https://vue-multiselect.js.org/#sub-custom-option-template
And here talks about the tags slots
https://vue-multiselect.js.org/#sub-slots
Here is an example:

Vue.js. Render of different kinds of child components according to a list of conditions

I'm new to not OOP, and VUE.JS especially.
I have a list of conditions, according to them I should show on the page several different kinds of components.
How can I render, for example, 2 TextInput components (or 3.. 10) dynamically and read the entered text in parent after clicking a button?
Thank you in advance.
You didn't provide any code, so I'm not sure what exactly you are trying to do.
If you want to display multiple components, just use v-for and specify conditions in v-if, which will detemine whether this particular component will be rendered:
<input
v-for="input in inputs"
v-if="input.show"
v-model="input.model"
:placeholder="input.label"
type="text"
>
<button #click="handleButtonClick()">Button text</button>
data: () => ({
inputs: [
{
label: 'input 1',
model: '',
show: true
},
{
label: 'input 2',
model: '',
show: true
}
]
}),
methods: {
handleButtonClick () {
console.log(this.inputs)
}
}
If you don't know the type of component you need to display you can use dynamic components.
In a nutshell this defers the type of component used at runtime based on a value.
Let's assume you have 2 different type of components
TextComponent
ImageComponent
You can have a list of items
data () {
return {
items: [
{
id: 1,
val: 'something',
type: 'TextComponent'
},
{
id: 2,
val: 'something',
type: 'ImageComponent'
}
]
}
}
Now you can iterate over the list and display the component based on type
<component v-for="item in items" :key="item.id" :is="item.type" v-model="item.value />
If type is not the exact name of the component, you can translate it inside the :is condition. Something like this.
:is="getComponentFromTag(item.type)"
And then write the conversion method
methods: {
getComponentFromTag (tag) {
switch (tag) {
case 'text':
return 'TextComponent'
case 'img':
return 'ImageComponent'
}
}
}
For the example above I'm assuming that items look like this:
items: [
{
id: 1,
val: 'something',
type: 'text'
},
{
id: 2,
val: 'something',
type: 'img'
}
]

Vue.js - Reactivity of a select whose value is the selected object and not its id

I'm using vue.js 2.3 and element-ui. I'm facing reactivity issues with a select dropdown whose value is the item picked.
Problems
The select is not automatically filled with the initial object value
Notices
If I change :value="item" to :value="item.name"
and form: {option: {name:'blue', price: ''}} to form: {option:'blue'}
It is working
Questions
Is there a way to make the select dropdown fully reactive when its value is not just a string or a id but rather the whole object that has been selected
https://jsfiddle.net/LeoCoco/aqduobop/
<
div style='margin-bottom:50px;'>
My form object :
{{form}}
</div>
<el-button #click="autoFill">Auto fill</el-button>
<el-select v-model="form.option" placeholder="Select">
<el-option v-for="item in options" :key="item.name" :label="item.name" :value="item">
</el-option>
</el-select>
</div>
var Main = {
data() {
const options = [
{name: 'blue', price: '100$'},{name: 'red', price: '150$'},
]
return {
currentItem: 0,
options,
form: {
option: {name:'', price: ''},
},
testForm: {
option:{name:'red', price: '150$'}
},
}
},
methods: {
autoFill() {
this.form = Object.assign({}, this.testForm); // -> Does not work
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
Your issue is that when the selected value is an object, then form.option needs to be the same object in order for it to be selected in the select list.
For example if I change the fiddle code to this, I think it works the way you expect.
var Main = {
data() {
const options = {
'color': [{name: 'blue', price: '100$'},{name: 'red', price: '150$'}],
'engine': [{name: '300hp', price: '700$'},{name: '600hp', price: '2000$'}],
}
let currentCategory = 'color'
return {
currentCategory,
currentItem: 0,
options,
form: {
option: options[currentCategory][0]
},
testForm: {
option:{name:'blue', price: '100$'}
},
}
},
methods: {
autoFill() {
this.form = Object.assign({}, this.testForm); // -> Does not work
}
}
}
var Ctor = Vue.extend(Main)
new Ctor().$mount('#app')
Here is your fiddle updated.
You said your values are coming from an ajax call. That means that when you set form.option you need to set it to one of the objects in options[currentCategory].