I'd like to call some callback in a React component whenever the input's value has changed.
simplified example should explain:
in a jsx file:
<input onKeyDown={this.props.changeCallback( VALUE_OF_INPUT )} ></input>
what should VALUE_OF_INPUT be?
Thanks!
There's not much React-specific about this. Use the event which is passed to the callback as the first argument:
onKeyDown={event => this.props.changeCallback(event.target.value)}
This approach (using a fat arrow function) also handles binding this, at the expense of creating a new anonymous function once per render. Note that calling bind essentially does the same thing, so this is my preferred option out of the two.
It's important to make the distinction between assigning a function to the onKeyDown attribute (as is done here) and calling the function (what you did).
<input value={this.state.value} onKeyDown={this.props.changeCallback.bind( this, this.state.value)} ></input>
Would do the job.
Another way to do it dynamic is give it a value callback this works well if pre populating the "value" from some var.
/* Better function for larger forms */
handleChange = (event) => {
var name = event.target.name;
var value = event.target.value;
this.setState({[name]: value});
}
/* set Default and callback to state */
valueCallback = (value, name) => {
if (this.state[name]) {
return this.state[name]
}
else {
return value;
}
}
then you can go ...
var defaut = '1234'; // or this.props.something.something;
<input type={type} name={name} value={this.valueCallback(defaut, name)} onChange={this.handleChange} />
Related
I am using vue3 and wonder how to pass data the correct way.
My Component structure is one table (items loaded via pinia store): XTableComponent
The XTableComponent has a child: XModalComponent. In the rendered table I have a button in each row. #click on that stores the current item in a data item
XTableComponent:
<template>
...that mentioned table in each line a button with #click and the item in the iteration as param
<x-model-component v-if="currentItem" :item="currentItem ref="x-modal"></x-modal-component>
</template>
<script>
export default {
data: () => {
return {
currentItem: {},
itemListStore: useItemListStore()
}
},
computed: {
itemList() {
return this.itemListStore.list
}
methods: {
showModal(item){
this.currentItem = item
this.$refs['x-modal'].show()
}
}
}
</script>
My Child component looks a bit like this:
XModalComponent:
<template>
....
<input v-model:value="innerItem.something" type="text">
<button #click="save">save</button>
</template>
<script>
export default {
props: {
item: Object
},
data: () => {
return {
innerItem: {}
}
}
mounted() {
this.innerItem = item
},
methods: {
save() {
console.log(this.innerItem) //this does not show the manipulated value of `something`
}
}
}
</script>
Now, if I manipulate the input in my child component, and trigger a click event, the value does not get changed on my data item ...
what did I get wrong in vue3 with reactiveness, proxeis and passing props?
p.s. my code is kind of pseude code here, so please be fair with me on typos, or obvious parts
that are missing
p.p.s. I am used to vue2 quite well, so maybe I mix concepts. please tell me that too.
p.p.p.s. my table renders correctly, the modal looks fine. i double checked all names and typos.
So, as we figured out, the problem came from the way innerItem.something was bound to the input, and some confusion around the v-model directive.
As a recap, the v-model directive is short-hand for setting a prop on a component and listening to an event which updates the value.
In Vue 2, that was:
<child-component
:value="myValue"
#input="(nevValue) => myValue = newValue"
/>
which is equivalent to
<child-component v-model="myValue"/>
and it allows a variable to be changed by parent as well as child ("two-way binding"). Note that property name and event matches that of a HTML input element (the "value" attribute and the "input" event), probably because it represents the most familiar case, where a value is bound to an input:
<input type="text" v-model="myText"/>
However, to allow for multiple two-way bindings on a component, Vue 2 also introduced a second way, which allows to bind to any of the child components props, not just "value". This is the .sync modifier:
<child-component :childComponentProp.sync="myVar"/>
which is equivalent to:
<child-component
:childComponentProp="myVar"
#update:childComponentProp="(newValue) => myVar = newValue"
/>
In Vue 3, they decided to unify the two, dropping .sync and instead allowing to pass a prop name to v-model similar to how slot names are passed to the v-slot directive, i.e. v-model:childComponentProp="myVar", and similar as v-slot alone is equivalent to v-slot:default, v-model alone is equivalent to v-model:modelValue. So it is equivalent to:
<my-component
:modelValue="myValue"
#update:modelValue="(nevValue) => myValue = newValue"
/>
But the above only applies for Vue components. When using v-model on an HTML input element, it sill behaves like in Vue 2 and binds to the "value" attribute and the "input" event. It is still equivalent to:
<input :value="myValue" #input="(nevValue) => myValue = nevValue"/>
However, that behavior is a special case of plain v-model (i.e. without a prop name). And I think this is where the confusion comes from.
Using v-model:value explicitly binds to the #update:value event, i.e. this
<input v-model:value="innerItem.something" type="text">
is equivalent to:
<input type="text"
:value="innerItem.something"
#update:value="(newValue) => innerItem.something = newValue"
/>
but that event is not sent by a plain HTML element.
So long long story short, you have to use v-model= instead of v-model:value= when binding to a native input element.
Does that make sense? Hope it helps.
I'm using Meteor with React. I have a really simple goal, but i have tried a lot and can't solve it for myself. I will show you my attemps below.
I want to create a form for the Ingredients. At the first moment there is only one input (for only one ingredient) and 2 buttons: Add Ingredient and Submit.
class IngredientForm extends Component {
render() {
return(
<form onSubmit={this.handleSubmit.bind(this)}>
<input type="text"/>
{ this.renderOtherInputs() }
<input type="button" value="Add Ingredient" onClick={this.addIngredient.bind(this)}>
<input type="submit" value="Submit Form">
</form>
);
}
}
So when I click Submit then all the data goes to the collection. When I click Add Ingredient then the another text input appears (in the place where renderOtherInputs() ).
I know, that Meteor is reactive - so no need to render something directly. I should underlie on the reactive data storage.
And I know from the tutorials the only one way to render something - I should have an array (that was based on collection, which is always reactive) and then render something for each element of that array.
So I should have an array with number of elements = number of additional inputs. that is local, so I can't use Collection, let's use Reactive Var instead of it.
numOfIngredients = new ReactiveVar([]);
And when I click Add button - the new element should be pushed to this array:
addIngredient(e) {
e.preventDefault();
let newNumOfIngredients = numOfIngredients.get();
newNumOfIngredients.push('lalal');
numOfIngredients.set(newNumOfIngredients);
}
And after all I should render additional inputs (on the assumption of how many elements I have in the array):
renderOtherInputs() {
return numOfIngredients.get().map((elem) => {
return(
<input type="text"/>
);
}
}
The idea is: when I click Add button then new element is pushed to the ReactiveVar (newNumOfIngredients). In the html code I call this.renderOtherInputs(), which return html for the as many inputs as elements I have in my ReactiveVar (newNumOfIngredients). newNumOfIngredients is a reactive storage of data - so when I push element to it, all things that depends on it should re-render. I have no idea why that is not working and how to do this.
Thank you for your help.
Finally I got the solution. But why you guys don't help newbie in web? It is really simple question for experienced developers. I read that meteor and especially react have powerful communities, but...
the answer is: we should use state!
first let's define our state object in the constructor of react component:
constructor(props) {
super(props);
this.state = {
inputs: [],
}
}
then we need a function to render inputs underlying our state.inputs:
renderOtherInputs() {
return this.state.inputs.map( (each, index) => {
return (
<input key={ index } type="text" />
);
});
}
and to add an input:
addInput(e) {
e.preventDefault();
var temp = this.state.inputs;
temp.push('no matter');
this.setState({
inputs: temp,
});
}
p.s. and to delete each input:
deleteIngredient(e) {
e.preventDefault();
let index = e.target.getAttribute('id');
let temp = this.state.inputs;
delete temp[index];
this.setState({
inputs: temp,
});
}
Using v-on:click I'd like to set a variable with the id of the div in Vue.JS - how do I reference this?
<div id="foo" v-on:click="select">...</div>
<script>
new Vue({
el: '#app',
data: {
},
methods: {
select: function(){
divID = this.id // ??
alert(divID)
}
}
})
</script>
You can extend your event handler with the event object $event. That should fit your needs:
<div id="foo" v-on:click="select($event)">...</div>
The event is passed on in javascript:
export default {
methods: {
select: function(event) {
targetId = event.currentTarget.id;
console.log(targetId); // returns 'foo'
}
}
}
As mentioned in the comments, `$event` is not strictly necessary, when using it as the only parameter. It's a nice reminder that this property is passed on, when writing it explicitly.
However, nobody will stop you from writing the short notation:
<div id="foo" #click="select">...</div>
Beware that the method will not receive the `$event` object when you add another parameter. You need to explicitly add it at the position you will handle it in the listener. Any parameter order will work:
<div id="foo" #click="select(bar, $event)">...</div>
To find more options of the v-on directive, you can look through the corresponding entry in the vue documentation:
Vue API Documentation - v-on
Inspired by #nirazul's answer, to retrieve data attributes:
HTML:
<ul>
<li
v-for="style in styles"
:key="style.id"
#click="doFilter"
data-filter-section="data_1"
:data-filter-size="style.size"
>
{{style.name}}
</li>
</ul>
JS:
export default {
methods: {
doFilter(e) {
let curTarget = e.currentTarget;
let curTargetData = curTarget.dataset;
if (curTargetData) {
console.log("Section: " + curTargetData.filterSection);
console.log("Size: " + curTargetData.filterSize);
}
}
}
}
Just to highlight another option than the selected answer for the same question, I have a delete button on a record and want to perform an action with the record's unique id (a number). I could do the selected answer as before:
<button :id="record.id" #click="del">×</button>
This leaves the unfortunate reality that my del function needs to pull the id attribute out of the javascript event, which is more about the API (the DOM) than my domain (my app). Also using a number as an element id isn't ideal and could cause a conflict if I do it more than once in a view. So here's something that's just as clear and avoids any future confusion:
<button #click="()=>del(record.id)">×</button>
methods: {
del(id) {
fetch(`/api/item/${id}`, {method:"DELETE"})
}
}
You see, now my del function takes the record id instead of an event, simplifying things.
Note that if you do this wrong, you will invoke your delete function immediately, which is not what you want. Don't do this:~~
<button #click="del(record.id)">×</button>
If you end up doing that, Vue will call the del function every time this html fragment is rendered. Using the anonymous function ()=>del(record.id) will return a function that's ready to be executed when the click event happens.
Actually #nirazul proved this is fine. Not sure what my issue was.
I am using Meteor with React. Consider this simple component below. There is a local mini-Mongo collection CommentsCollection. The component will insert a row in it when componentWillMount will be called. getMeteorData will return the first record in the collection and we'll be able to modify the title. Problem: if I place my cursor at the start of the title and start typing, after the first character update the cursor will jump to the end of the string and the rest of my typing will be placed there. How do I work around this?
CommentsCollection = new Meteor.Collection(null); // Local mini-mongo collection
EventTestComponent = React.createClass({
mixins : [ReactMeteorData],
componentWillMount(){
CommentsCollection.insert({title:"test title", message:"some test message"});
},
getMeteorData(){
return {
comment: CommentsCollection.findOne()
};
},
handleTitleChange(e){
CommentsCollection.update({_id: this.data.comment._id}, {$set:{title: e.target.value}});
},
render(){
if(this.data.comment) {
return (
<div>
<input type="text" value={this.data.comment.title} onChange={this.handleTitleChange}/>
</div>
);
}else{
return <div>Loading...</div>
}
}
});
I came up with this solution right after I posted the question:
<input type="text"
defaultValue={this.data.comment.title}
onKeyUp={this.handleTitleChange}/>
So: change value to defaultValue, and onChange to onKeyUp. Works like a charm!
It is said that,
A good rule of thumb is that if you're using jQuery to manipulate any
DOM elements, you're probably doing it wrong.
And:
Needing access to an element from a helper function indicates that you
are trying to use a procedural coding style rather than a
template-driven style.
I have a simple number input that I wish to translate into a more comprehensible currency outside it so that the user understands what he is doing. I wanted to do this:
<input type="number" class="raw-price">
<p>Price in USD: {{priceInUsd}}</p>
And then define a helper:
Template.myTemplate.helpers({
priceInUsd: function() {
var rawPrice = $('.raw-price').val()
//perform calculation
return calculationResult
}
})
First of all, this isn't working (I don't really know why). Second, this goes against the "rules" I posted above. How am I supposed to do it? I probably could do this the same way using an event listener instead, but that would still be the wrong approach, I assume.
Flash update!
Actually! Here's a better solution: reactive variables! If you wish to keep your rawPrice in this one template, just install the standard reactive-var package:
meteor add reactive-var
And go at it this way:
Template:
<input type="number" class="raw-price">
<p>Price in USD: {{priceInUsd}}</p>
onCreated:
Template.myTemplate.onCreated(function() {
this.rawPrice = new ReactiveVar;
this.rawPrice.set(''); // not sure what you want to preset your value to
});
Helper:
Template.myTemplate.helpers({
priceInUsd: function() {
var rawPrice = Template.instance().rawPrice.get()
//perform calculation
return calculationResult
}
})
Event:
Template.myTemplate.events({
"change .raw_price": function (evt, template) {
template.rawPrice.set($(evt.currentTarget).val());
}
});
Previous (accepted) answer
According to most examples of two-way data binding in meteor I have seen, best case would probably be to use a helper, an event and a Session variable, like so.
Template:
<input type="number" class="raw-price">
<p>Price in USD: {{priceInUsd}}</p>
Helper:
Template.myTemplate.helpers({
priceInUsd: function() {
var rawPrice = Session.get('rawPrice');
//perform calculation
return calculationResult
}
})
Event:
Template.myTemplate.events({
"change .raw_price": function (evt) {
Session.set("rawPrice", $(evt.currentTarget).val());
}
});
Sadly, you're using a session variable, but it is still better than using a collection for such a local thing, like I saw in other examples...