How can I work with just values in react-select onChange event and value prop? - react-select

I try to use react-select in my reactjs app, but I have a problem with the onChange event. onChange is supposed to send two arguments. The first is supposed to be the selected value, but instead of the selected value, the whole option item is passed as the selected value.
For instance
I have an array of option items like options=[{ id: '1', name: 'A'},{ id: '2', name:'B'}]
I set getOptionValue = (i) => i.id; and getOptionLabel = (i)=>i.name;
When select the second item onChange(value) is passed the second option as the value argument ({id:'2',name:'B'}) instead of the value of the second option ('2').
This behavior is inconsistent with most input components out there. I would expect onChange to be passed the value of the item, and for the item itself I would expect another event like onItemSelected or something like that.
Also, when I set value={'2'} (controlled component), the component doesn't show the selected item.
I must say that I use AsyncSelect with loadOptions.
How can I make it work with just simple values, instead of option objects?
If this can't happen I have to abandon react-select for another similar component.

AFAIK currently there's no way to make React-Select work internally with just the value. What I'm doing in my application is implementing a layer to retrieve the object going down, and extract the value going up. Something like this (this is simplified, you may need more validation or handling depending on your application):
const Select extends Component {
handleChange(newSelected) {
// this.props.handleChange is your own function that handle changes
// in the upper scope and receives only the value prop of the object
// (to store in state, call a service, etc)
this.props.handleChange(newSelected.value);
}
render() {
// Assuming you get the simple value as a prop from your store/upper
// scope, so we need to retrieve the option object. You can do this in
// getDerivedStateFromProps or wherever it suits you best
const { options, value } = this.props;
const selectedOption = options.find(option => option.value === value)
<ReactSelect
options={options}
value={selectedOption}
onChange={this.handleChange}
{...props}
/>
}

Related

Vue v-model issue when using a computed setter

I want to create an input field that the user can fill out. The catch is that I don't want them to fill this field in with special characters. Currently, I have this html setup:
<input class="rc-input-editing" id="bioInput" type="text" v-model="wrappedBioName">
And this Vue.js setup (As you can see, I'm trying to approach this problem using a computed setter) :
data () {
return {
newBioName: '',
}
},
computed: {
wrappedBioName: {
get () {
alert('getting new name!')
return this.newBioName
},
set: function (newValue) {
const restrictedChars = new RegExp('[.*\\W.*]')
if (!restrictedChars.test(newValue)) {
this.newBioName = newValue
}
}
}
Currently, my issue is that the client is able to continue filling out the text input field, even when this.newBioName isn't updating. In other words, they are able to enter special characters into the input field, even though the this.newBioName isn't being updated with these special characters.
This behavior is different than what I'm expecting, given my current understanding of v-model. Based on what I've read until now, v-model binds the input element to some vue instance data, and that vue instance data to the input element (two way binding). Therefore, I'm expecting that the text in the text input field will directly match the value of this.newBioName.
Clearly, I'm missing something, and would appreciate a second pair of eyes!
Vue.js two way binding system doesn't work as you expected. Each binding process works one way each time. So, the thing you should do is not to let the input text change.
Try keypress event instead of computed property like this:
<input class="rc-input-editing" id="bioInput" type="text" v-model="newBioName" #keypress="nameKeyPressAction">
data() {
return {
newBioName: ""
};
},
methods: {
nameKeyPressAction(event) {
const restrictedChars = new RegExp("[.*\\W.*]");
const newValue = this.newBioName + event.key;
if (!restrictedChars.test(newValue))
this.newBioName = newValue;
return event.preventDefault();
}
}
Edit:
When you set a data property or a computed property as v-model of an input, vue associates them and yet, if user updates dom object via the input, property's setter is triggered and the process ends here. On the other hand, when you change the value of the property on javascript side, vue updates the dom object and this process also ends here.
In your sample code, it seems like you expect that the computed property's getter to set the dom again but it can't. The property is already updated via dom change and it can't also update it. Othervise, there might occur infinite loop.

Quasar: Is there any way to get the value typed into a QSelect with use-input before it gets removed on blur?

Per the docs you can add the attribute use-input to a QSelect component to introduce filtering and things of that nature: https://quasar.dev/vue-components/select#Native-attributes-with-use-input.
However, if you type something into one of these fields and click outside of it, the text gets removed.
Is there any way to grab that text in Vue before it gets removed and do something with it?
Since v1.9.9 there is also a #input-value event described in the q-select api.
As the api says it's emitted when the value in the text input changes. The new value is passed as parameter.
In the examples there's a filter function, so there you can save it in a data variable:
methods: {
filterFn (val, update, abort) {
update(() => {
this.myDataVariable = val;
})
}
}

What is the best way to initialize a state in ReactNative with custom values on a Modal?

So, I have a specific problem where i need to initialize a state with custom values that comes from the parent component props.theValue. But the problem is that the props.theValue isn't ready when we set the state. So this line should never work:
const [grams, setGrams] = useState(props.theValue)
The scenario:
We have a list of food on the Parent Screen, when the users Clicks on it, it opens a modal (Like a PopUp) with some options to choose before the selected item gets manipulated
What I tried?
Set initial flag useState(-1) to indicate it should set the value from props but again, the props isn't ready in this state
Save the state on the parent and pass the state with the set Function to the modal. But still it doesn't seems to be right
useEffect but whenever i call setGrams it get a Loop Rendering error, also the props.theValue is still undefined at this point
Example:
In this case, the both TextInput must have a initial value that comes
from the Parent Component and must be manipulated.
You could use Redux for manipulating your data between Components or you can try something like:
set a Global variable:
let previousObj = null
and then set a timeout to update your data only once, even if you set Modal invisible and Visible again
setTimeout(() => {
if(food.grams != undefined && previousObj != food){
previousObj = food
setGrams(""+food.grams)
}
}, 100)
You can either handle it in the child with default value or don't render the component in parent until the value is available:
{theValue && <Component theValue={theValue} />}
You can use useEffect to update the default value as well:
const [grams, setGrams] = useState(0)
useEffect(() => {
if (theValue) {
setGrams(theValue);
}
}, [theValue])

How to use Vue v-model binding in Vuetify components together with computed properties and Vuex?

I'm trying to use v-radio-group in conjunction with computed values from Vuex as described similarly here.
Example codepen of the issue I'm facing is here
Whenever a radio button is clicked, a Vuex mutation is called to save the selected value in the state.
However it can be the case that some validation fails inside the mutation and that therefore the value is not changed in the state as expected.
Regardless of what value ends up in the Vuex state, the radio buttons do not truly reflect the current state.
E.g. in the codepen snippet I'd expect the second option (Option 1) never to show as chosen, as the corresponding state is always 0.
As far as I can see this behavior is not only happening when using v-radio-groups.
It happens with all Vuetify components using v-model and computed getters/setters.
So e.g. Vuetifys v-text-input/v-text-field and v-select also show the same behavior.
To sum it up, my questions are the following:
Why is the second option in my codepen example getting selected even as the corresponding state is different?
How can I achieve the expected result (Having Option 1 never shown as selected, even when it is clicked)?
As far as I know Vuetify keeps its own state in their components like v-radio-group.
To change it you need to send updated props. Then it will react and update its own state.
The trouble is that you are performing validation in a mutation. Which is a bad practice in my opinion.
I will show you how to "block" changing state and update v-radio-group so its own state corresponds to what is actually in your $store.state.radioState.
And I will spend some more time to figure out how to performe it in on mutation ;-)
This is not a perfect solution >> my codepen
Your mutation just updates the state.
// store.js
mutations: {
setRadioState (state, data) {
state.radioState = data;
},
},
Your set method do the validation.
// component
computed: {
chosenOption: {
get () {
return this.$store.state.radioState;
},
set (value) {
if (value !== 1) {
this.$store.commit('setRadioState', value)
} else {
const oldValue = this.$store.state.radioState
this.$store.commit('setRadioState', value)
this.$nextTick(() => {
this.$store.commit('setRadioState', oldValue)
})
}
}
}
}
What happens in the set when it fails validation? You save current state to oldValue, you update state so it corresponds to v-radio-group component. And in the $nextTick you change it right back to oldValue. That way v-radio-group gets updated props and change its state to yours.

Vue - same mutation refreshes (or not!) components depending on which component it is called from?

I have problem understanding why THE SAME mutation fails to refresh data displayed in components (although it does change underlying vuex store data!) if it is called from one of the components, but it does refresh the data if called from another component?
I am updating Filter objects stored in store this way: state.report.filters[], where filters is array of Filter objects.
const state = {
report: {
filters: [], // array of Filter objects
...
}
}
My mutation looks for a filter in the array and substitutes the whole Filter object.
const mutations = {
setFilter: (state, newFilterValue) => {
let changedFilter = state.report.filters.find(filter => {
return filter.fieldName === newFilterValue.fieldName;
});
changedFilter = newFilterValue;
}
}
The mutation is called from a method of Filter class defined like this (separate module):
import { store } from './store';
export class Filter {
constructor ({
...
} = {}) {
this.operators = []; // Array of Operator objects
this.value = []; // Array of values - in this case Dates
};
updateOperator (operatorName) { // this mutation refreshes components when executed
this.operator[0] = new Operator(operatorName);
store.commit('setFilter', this); // whole object passed to the mutation
};
updateValue (newValue) { // this mutation changes store value, but fails to refresh components
this.value[0] = newValue; // newValue is a Date
store.commit('setFilter', this);
};
};
The app displays data in rows (each Filter has a separate row), each row contains cells, of which one contains components dedicated to Filter's value and Operator. These dedicated components receive as props callback functions which are methods of the Filter object. They execute the callback functions when a new value is entered passing the value to the Filter which then updates a relevant property and calls the mutation passing in both cases the whole Filter object as payload.
// TABLE CELL COMPONENT displaying filter value and operator
<template>
<td>
<operator-component
:iconName="proppedFilterObject.operator.iconName"
:callback="proppedFilterObject.updateOperator.bind(proppedFilterObject)"
></operator-component>
<value-component
:date="proppedFilterObject.value[0]"
:callback="proppedFilterObject.updateValue.bind(proppedFilterObject)"
></value-component>
</td>
</template>
<script>
export default {
props: ['proppedFilterObject'] // whole filter object
};
</script>
// OPERATOR COMPONENT
<template>
<div #click.stop="chooseOperator">
{{ iconName }} // some operator value display
</div>
</template>
<script>
export default {
methods: {
chooseOperator () {
const modal = new ChooseOperatorModal({
callback: this.callback // this displays another modal for receiving data. The modal calls the callback.
});
},
},
props: ['callback', 'iconName']
};
</script>
// VALUE COMPONENT
<template>
<date-picker v-model="computedDate"> // THIRD PARTY COMPONENT
</date-picker>
{{ date }} // additional display to verify if there's a problem within 'date-picker'
</template>
<script>
import DatePicker from 'vue2-datepicker'; // THIRD PARTY COMPONENT
export default {
components: { DatePicker },
computed: {
computedDate: {
get: function () {
return this.date;
},
set: function (newValue) {
this.callback(newValue);
}
}
},
props: ['callback', 'date']
};
</script>
So, if eg. I enter new operator value from Operator component, everything refreshes. When I enter a new value in the value component, the mutation is executed and store value changed, but displayed data are not refreshed. However, if afterwards I change an operator all the components will refresh and value will get displayed. Even if I change operator in a different Filter object(!). Ie:
a) Change in report.filters[0].value - display not refreshed, but...
b) then change report.filters[1].operator - both report.filters[1].operator AND PREVIOUSLY CHANGED report.filters[0].value get refreshed(?!).
What can be a reason of such behaviour? Where to look for the problem?
Some additional remarks:
1) I am using a third party component "vue2-date-picker" for date choice and display. However it does not seem to be responsible for the problem, as if I try to display the new value just in {{ }} notation it behaves the same way. I have used the date picker in other components and there it functions correctly as well.
2) In the code samples I left out most imports/exports and other seemingly irrelevant elements to keep the question reasonably short.
There are a lot of problems with the code and several of them are contributing to the problems you're seeing. A full, thorough answer that addresses all of these problems would be ridiculously long so instead I will skim through them without going into huge amounts of detail. You will need to do some further reading and experimentation to understand each of these topics properly.
Let's start with this line in the mutation:
changedFilter = newFilterValue;
This line assigns a new value to the local variable changedFilter. That's all. As it's the last line of the mutation the net result is that it doesn't really do anything.
Presumably your intent was to update the array state.report.filters, replacing the old entry with a new entry. However, just updating a local variable isn't going to do that.
At this point you may be wondering 'If that doesn't do anything, then why is the state in my store changing?'. I'll come to that in a moment but first let me prove to you that your existing code does nothing.
Try removing the code inside setFilter completely. Just leave an empty function. Then try clicking around in the UI just like you did before. You'll find that the store state updates just the same as it did before, even though you've removed the code to update the array.
The correct way to implement that mutation would be to use findIndex to find the relevant index and then use either Vue.set or the array's splice method to update the array accordingly. That will change the item in the array. However...
This brings us back to the earlier question. Why is the state updating if the mutation does nothing?
This is because you're using the same object in multiple places. The Filter object held in the array is the same object that your UI is editing. There are no copies being taken, there is just a single object. So when you change the properties of that object inside updateOperator or updateValue this will immediately be reflected inside the store. Calling the setFilter mutation is just asking the store to replace an object with itself.
There's nothing specific to Vue about this. This is just the standard behaviour of reference types in JavaScript. It is also common with many other programming languages that don't directly expose pointers. It can be useful to learn a little about how pointers work in other languages as it will give you a better initial mental model before attempting to understand how reference types behave in JavaScript. Understanding the difference between 'by value' and 'by reference' may also be a useful starting point.
The next topic to cover is reactivity, which very much is a Vue topic.
Specifically, there are certain changes that Vue can't detect. These are usually referred to as the reactivity caveats. You can find more about them in the official documentation:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
https://v2.vuejs.org/v2/guide/list.html#Caveats
There are at least two lines in your code that violate these rules:
this.operator[0] = new Operator(operatorName);
and
this.value[0] = newValue;
You can't set array entries directly by index. The array will update but it won't trigger any reactive dependencies within Vue. Instead you need to use either Vue.set or one of the array methods, e.g. push, pop, splice, etc.. In this example you could use splice.
e.g. Using Vue.set:
Vue.set(this.value, 0, newValue);
e.g. Using splice:
this.value.splice(0, 0, newValue);
Why does all of this matters?
Well Vue will only re-render a component if its reactive dependencies have changed. They are very similar to computed properties in that regard. Here's how it works...
Vue compiles the template down to a function. That function is referred to as the render function. When rendering a component Vue calls the render function and that function returns a description of how to render the component. Any reactive properties that are touched while that function is running will be recorded as dependencies. If, at some point in the future, the value of one of those reactive properties changes then Vue will rerun the render function to generate a new rendering of that component.
There are two key points to take out of this description:
If you fall foul of one of the reactivity caveats then Vue won't know the dependency has changed, so it won't re-render the component.
The render function runs as a whole. It doesn't just target a small chunk of the template, it always runs the whole thing.
So if you change a dependency in a non-reactive way (i.e. one of the caveats) it won't trigger a rendering update. But if you subsequently update a dependency properly, Vue will detect that and will rerun the render function. When it runs it will run the whole thing, so any new values will be picked up, even if they weren't detected when they changed.
It isn't immediately clear to me which rendering dependency is causing your component to re-render. However, it only needs one of them to change in a detectable manner. Any other changes will then get pulled in incidentally when the render function runs and reads their current values.
That covers why your code isn't working. However, I would also worry about your decision to introduce a Filter class. I understand how that may be appealing if you've come from some other OO environment but it isn't typically how Vue is used. It is possible to make it work but you will need a good understanding of both JavaScript reference types and the Vue reactivity system to avoid falling through the cracks. There is no reason why using a specific class to hold your data can't be made to work but in practice it usually ends up being less maintainable than not using such a class. A more typical Vue approach would be to use simple, anonymous objects/arrays to hold the data and then for the data owner (either a component or store module) to be responsible for making any mutations to that data. Events are used to pass changes up the component hierarchy rather than callback props.
Ultimately you will need to judge whether the Filter class is justified but it is probably not what future maintainers of your code will be expecting.