Algolia vue-places: Clear input after selection - javascript

I'm using the vue-places component to render an Algolia places search input in Vue - it works brilliantly.
After a user selects / accepts a suggestion from the search dropdown, I want to clear the input and allow them to search again. Based on the standard example provided, I have tried to set form.country.label v-model value back to null in the change handler:
<template>
<places
v-model="form.country.label"
placeholder="Where are we going ?"
#change="selectLocation"
:options="options">
</places>
</template>
<script>
import Places from 'vue-places';
export default {
data() {
return {
options: {
appId: <YOUR_PLACES_APP_ID>,
apiKey: <YOUR_PLACES_API_KEY>,
countries: ['US'],
},
form: {
country: {
label: null,
data: {},
},
},
};
},
components: {
Places,
},
methods: {
selectLocation: function(event: any) {
if (event.name !== undefined) {
/**
* implementation not important
*/
this.form.country.label = null;
}
}
}
}
</script>
The selectLocation method fires as expected - but I cannot find any way to rest the input value to be empty.
How can I update a data value from a component method and have this reflected in a referencing component - in this case the Algolia places input?

The issue occurs because of how vue-places is proxying change events from the underlying implementation. When a change event is received, it broadcasts the same event and then updates the input value:
Places.vue:
this.placesAutocomplete.on('change', (e) => {
this.$emit('change', e.suggestion);
this.updateValue(e.suggestion.value);
});
This means that any attempt to set a value in our change handler will immediately be overridden.
My solution is to create a ref to the vue-places instance and then use the built-in Vue.nextTick to use the internal places.js setVal method after the call to updateValue:
methods: {
selectLocation: function(event: any) {
if (event.name !== undefined) {
// do something with the value
Vue.nextTick(() => {
// clear the input value on the next update
this.$refs.placesSearch.placesAutocomplete.setVal(null);
});
}
}
}

Related

Vue.js 2: Watch but not on initial data fetch

I'm new in the Vueniverse (using Vue.js 2) and I'm struggling with watch. On mounted, I call an API and set the radio button to the value I got from the API, so basically I have two radio buttons with values 1 and 0 (true/false).
I think the watcher works correctly, because it does trigger when the value is changed. However, I don't want it to trigger on the initial change - that's when I first set the value from the backend.
I've tried with different lifecycle hooks, such as beforeCreated, created and so on and it always triggers.
Probably it's something easy to do but I can't figure out how and don't find information on the Internet (might using the wrong keywords).
The code:
import axios from "axios";
export default {
name: 'Settings',
data: () => ({
/* set motionSensor to null initially */
motionSensor: null
}),
mounted() {
/* get the initial value from the backend, however this triggers the watcher */
axios
.get('http://localhost:8000/status.json')
.then(response => {
response.data['motionsensor'] ? this.motionSensor = "1" : this.motionSensor = "0";
})
},
watch: {
motionSensor: function(val) {
alert('Motion sensor value is now: ' + val)
}
}
}
Try to take advantage from the old value which is 2nd parameter of the watch handler :
watch: {
motionSensor: function(val, oldVal) {
if (oldVal !== null) {
alert('Motion sensor value is now: ' + val)
}
}
}

Vue.js 2: Cant useTwo-way Computed Property in combination with vuex

I cant get Two-way Computed Property in combination with vuex to work.
If there are input changes I want to set getIsUnsavedData to true and "copy"/commit the changes into a new variable $store.state.authenticatedUser.changedData. After there is any change I want the input to get() its value from $store.state.authenticatedUser.changedData instead of $store.state.authenticatedUser.data to display the change.
At fist everything works like expected. If there are input changes, the changed value will be replicated in the $store.state.authenticatedUser.changedData property. The getIsUnsavedData value changes to true and the get() points to the replicated data.
There is only one bug left. Suddenly the computed property never changes although the vuex store is updating correctly. The set() function still gets called, but the get() doesn't .
<ui-textbox #change="valueChanged" v-model="userName"></ui-textbox>
// ...
computed: {
userName: {
get() {
return this.$store.getters.getIsUnsavedData ? this.$store.state.authenticatedUser.changedData.name : this.$store.state.authenticatedUser.data.name
},
set(value) {
this.$store.commit('setChangedUserData', {
key: 'name',
value: value
})
}
}
},
methods: {
valueChanged() {
this.$store.commit('setUnsavedState', true)
}
},
// ....
Try to use mine library
https://github.com/yarsky-tgz/vuex-dot
it's done for such situations, to make code footprint of setters/getters in your code much less.
<template>
<form>
<input v-model="name"/>
<input v-model="email"/>
</form>
</template>
<script>
import { takeState } from 'vuex-dot';
export default {
computed: {
...takeState('user')
.expose(['name', 'email'])
.dispatch('editUser')
.map()
}
}
</script>
(updated to reflect Andor's input)
v-model can point to a computed property but it needs to handle work a bit differently than when it just refers to data.
vue's doc
applying the section on two-way computed properties:
<ui-textbox v-model="userName"></ui-textbox>
// ...
computed: {
userName: {
get () {
return this.$store.state.obj.userName
},
set (value) {
this.$store.commit('updateUserName', value)
}
}
}
I might be missing something about what you're doing but this is how I'd start to solve the problem.

What's the correct way to pass props as initial data in Vue.js 2?

So I want to pass props to an Vue component, but I expect these props to change in future from inside that component e.g. when I update that Vue component from inside using AJAX. So they are only for initialization of component.
My cars-list Vue component element where I pass props with initial properties to single-car:
// cars-list.vue
<script>
export default {
data: function() {
return {
cars: [
{
color: 'red',
maxSpeed: 200,
},
{
color: 'blue',
maxSpeed: 195,
},
]
}
},
}
</script>
<template>
<div>
<template v-for="car in cars">
<single-car :initial-properties="car"></single-car>
</template>
</div>
</template>
The way I do it right now it that inside my single-car component I'm assigning this.initialProperties to my this.data.properties on created() initialization hook. And it works and is reactive.
// single-car.vue
<script>
export default {
data: function() {
return {
properties: {},
}
},
created: function(){
this.data.properties = this.initialProperties;
},
}
</script>
<template>
<div>Car is in {{properties.color}} and has a max speed of {{properties.maxSpeed}}</div>
</template>
But my problem with that is that I don't know if that's a correct way to do it? Won't it cause me some troubles along the road? Or is there a better way to do it?
Thanks to this https://github.com/vuejs/vuejs.org/pull/567 I know the answer now.
Method 1
Pass initial prop directly to the data. Like the example in updated docs:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
But have in mind if the passed prop is an object or array that is used in the parent component state any modification to that prop will result in the change in that parent component state.
Warning: this method is not recommended. It will make your components unpredictable. If you need to set parent data from child components either use state management like Vuex or use "v-model". https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Method 2
If your initial prop is an object or array and if you don't want changes in children state propagate to parent state then just use e.g. Vue.util.extend [1] to make a copy of the props instead pointing it directly to children data, like this:
props: ['initialCounter'],
data: function () {
return {
counter: Vue.util.extend({}, this.initialCounter)
}
}
Method 3
You can also use spread operator to clone the props. More details in the Igor answer: https://stackoverflow.com/a/51911118/3143704
But have in mind that spread operators are not supported in older browsers and for better compatibility you'll need to transpile the code e.g. using babel.
Footnotes
[1] Have in mind this is an internal Vue utility and it may change with new versions. You might want to use other methods to copy that prop, see How do I correctly clone a JavaScript object?.
My fiddle where I was testing it:
https://jsfiddle.net/sm4kx7p9/3/
In companion to #dominik-serafin's answer:
In case you are passing an object, you can easily clone it using spread operator(ES6 Syntax):
props: {
record: {
type: Object,
required: true
}
},
data () { // opt. 1
return {
recordLocal: {...this.record}
}
},
computed: { // opt. 2
recordLocal () {
return {...this.record}
}
},
But the most important is to remember to use opt. 2 in case you are passing a computed value, or more than that an asynchronous value. Otherwise the local value will not update.
Demo:
Vue.component('card', {
template: '#app2',
props: {
test1: null,
test2: null
},
data () { // opt. 1
return {
test1AsData: {...this.test1}
}
},
computed: { // opt. 2
test2AsComputed () {
return {...this.test2}
}
}
})
new Vue({
el: "#app1",
data () {
return {
test1: {1: 'will not update'},
test2: {2: 'will update after 1 second'}
}
},
mounted () {
setTimeout(() => {
this.test1 = {1: 'updated!'}
this.test2 = {2: 'updated!'}
}, 1000)
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app1">
<card :test1="test1" :test2="test2"></card>
</div>
<template id="app2">
<div>
test1 as data: {{test1AsData}}
<hr />
test2 as computed: {{test2AsComputed}}
</div>
</template>
https://jsfiddle.net/nomikos3/eywraw8t/281070/
I believe you are doing it right because it is what's stated in the docs.
Define a local data property that uses the prop’s initial value as its initial value
https://vuejs.org/guide/components.html#One-Way-Data-Flow
Second or third time I run into that problem coming back to an old vue project.
Not sure why it is so complicated in vue, but it can we done via watch:
export default {
props: ["username"],
data () {
return {
usernameForLabel: "",
}
},
watch: {
username: {
immediate: true,
handler (newVal, oldVal) {
this.usernameForLabel = newVal;
}
},
},
Just as another approach, I did it through watchers in the child component.
This way is useful, specially when you're passing an asynchronous value, and in your child component you want to bind the passed value to v-model.
Also, to make it reactive, I emit the local value to the parent in another watcher.
Example:
data() {
return {
properties: {},
};
},
props: {
initial-properties: {
type: Object,
default: {},
},
},
watch: {
initial-properties: function(newVal) {
this.properties = {...newVal};
},
properties: function(newVal) {
this.$emit('propertiesUpdated', newVal);
},
},
This way I have more control and also less unexpected behaviour. For example, when props that passed by the parent is asynchronous, it may not be available at the time of created or mounted lifecycle. So you can use computed property as #Igor-Parra mentioned, or watch the prop and then emit it.
Following up on Cindy's comment on another answer:
Be carful. The spread operator only shallow clones, so for objects
that contain objects or arrays you will still copy pointers instead of
getting a new copy.
Indeed this is the case. Changes within objects inside arrays will still propagate to your components even when a spread operator is employed.
Here was my solution (using Composition API):
setup() {
properties = ref([])
onMounted(() => {
properties.value = props.initialProperties.map((obj) => ({ ...obj }));
})
}
This worked to set the values and prevent them from getting changed, even if the data was changed in the parent component.

Why parent component does not update input component when setting state?

I have a input component like this:
let InputBasic = React.createClass({
displayName: 'InputBasic',
path: '',
componentWillMount: function () {
this.path = this.props.storePath + '.' + this.props.storeIndex;
},
getInitialState: function () {
return { };
},
getError: function () {
if (!Array.isArray(this.props.formErrorFields)) {
return false;
};
__this = this;
let hasError = false;
this.props.formErrorFields.forEach(function (index) {
if (__this.props.name == index.name) {
hasError = true;
return;
}
});
return hasError;
},
sendToValidation: function() {
if (this.props.rules) {
bom.form.validateInput(
/* Vars here */
);
}
},
getClassName: function () {
if(this.getError()) {
return 'input-basic has-error';
}
else {
return 'input-basic';
}
},
onBlur: function () {
this.sendToValidation();
},
handleChange: function (event) {
this.setState({ value: event.target.value });
},
render: function () {
return React.createElement('input',
{
type: this.props.type,
placeholder: this.props.placeholder,
style: this.props.style,
onChange: this.handleChange,
onBlur: this.onBlur,
className: this.getClassName()
}
);
}
});
The idea is to validate input field on blur. If the input doesn't pass the validation, the containing form state is altered. The form setState() function is passed to the input as a prop, then to the validation and so on.
The logic works in setting the form state. The form state is set accordingly. I also have a another component, which is dependant on the form state and that works perfectly. However, the input component is not updated as I was expecting.
In following scenario I have problems:
Empty, initial input -> Click to trigger onBlur -> validation fails -> input component updates
Type in to input -> Click to trigger onBlur -> validation success -> input component updates
Remove text again from input -> Click to trigger onBlur -> validation fail -> input component does not update
Add text again to input -> Click to trigger onBlur -> validation succes -> input component does update
This is peculiar, since other elements inside form are updated. If I add forceUpdate() to the onBlur function, the component updates as expected. However, this solution feels hacky, especially because I don't understand why I don't get the desired effect otherwise.
Things which came in to my mind while trying to solve this:
Can this be caused by the fact, that the component is triggering the update process which eventually points to itself? What I try to ask here is that is it a problem that I am passing the setState function down as a prop?
Are input components treated somehow differently, when it comes to updating?
EDIT:
Could this be caused because of onChange function? It is not in the example, since SO nagged about too long code sample.
MORE EDIT:
Apparently how I assign the value to the state affects if the update on children is launched.
This is the function which I use to update the state of the input parent:
bom.form = {
getErrorField: function (fields, name, text) {
let newFields = fields;
if (fields[name]) {
newFields[name].hasError = true;
newFields[name].errorText = text;
}
return newFields;
},
validateInput: function (inputValue, inputName, inputRules, setFormState, formFields) {
let fields = formFields;
if (!inputValue && inputRules.required) {
let text = 'This field is required.';
fields = this.getErrorField(formFields, inputName, text);
}
state = {};
state.fields = fields;
return setFormState(state);
}
};
So, calling validateInput() my I set parent component state of the input.
This does not launch the update in the input component. However, if I change state.fields assignment in example to this:
state.fields = { password: {}, username: {}}
It launches the update as desired. And of course, in this example the state is not what I want, this is just for presentational purposes.
The peculiar thing is that in both of these cases the other, different child component (which I call fieldMessage) updates. It is rendered this way:
return React.createElement('p', null, this.getText());
The getText() function returns parent component state, which is pointed to right field.
So I have became in to conclusion, that my problem has something to do how I assign the state object to setState() function. Am I correct? Can this happen because I pass the current state to the child function and am I possibly using object reference to original in a wrong place, or something like that?
You are not binding your onBlur handler to your react class. In particular:
render: function () {
return React.createElement('input',
{
type: this.props.type,
placeholder: this.props.placeholder,
style: this.props.style,
onChange: this.handleChange,
onBlur: this.onBlur.bind(this),
className: this.getClassName()
}
);
}
This is because, this.sendToValidation(); ends up being called bound to the event target.
I solved the problem by assigning the state.fields like this:
state.fields = Object.create(fields);
I assume that originally the fields were pointing to original state object reference and therefore did not launch the update. However, this is really just my guess and if somebody can clarify the behaviour to me I would be very grateful.

Is there a proper way of resetting a component's initial data in vuejs?

I have a component with a specific set of starting data:
data: function (){
return {
modalBodyDisplay: 'getUserInput', // possible values: 'getUserInput', 'confirmGeocodedValue'
submitButtonText: 'Lookup', // possible values 'Lookup', 'Yes'
addressToConfirm: null,
bestViewedByTheseBounds: null,
location:{
name: null,
address: null,
position: null
}
}
This is data for a modal window, so when it shows I want it to start with this data. If the user cancels from the window I want to reset all of the data to this.
I know I can create a method to reset the data and just manually set all of the data properties back to their original:
reset: function (){
this.modalBodyDisplay = 'getUserInput';
this.submitButtonText = 'Lookup';
this.addressToConfirm = null;
this.bestViewedByTheseBounds = null;
this.location = {
name: null,
address: null,
position: null
};
}
But this seems really sloppy. It means that if I ever make a change to the component's data properties I'll need to make sure I remember to update the reset method's structure. That's not absolutely horrible since it's a small modular component, but it makes the optimization portion of my brain scream.
The solution that I thought would work would be to grab the initial data properties in a ready method and then use that saved data to reset the components:
data: function (){
return {
modalBodyDisplay: 'getUserInput',
submitButtonText: 'Lookup',
addressToConfirm: null,
bestViewedByTheseBounds: null,
location:{
name: null,
address: null,
position: null
},
// new property for holding the initial component configuration
initialDataConfiguration: null
}
},
ready: function (){
// grabbing this here so that we can reset the data when we close the window.
this.initialDataConfiguration = this.$data;
},
methods:{
resetWindow: function (){
// set the data for the component back to the original configuration
this.$data = this.initialDataConfiguration;
}
}
But the initialDataConfiguration object is changing along with the data (which makes sense because in the read method our initialDataConfiguration is getting the scope of the data function.
Is there a way of grabbing the initial configuration data without inheriting the scope?
Am I overthinking this and there's a better/easier way of doing this?
Is hardcoding the initial data the only option?
extract the initial data into a function outside of the component
use that function to set the initial data in the component
re-use that function to reset the state when needed.
// outside of the component:
function initialState (){
return {
modalBodyDisplay: 'getUserInput',
submitButtonText: 'Lookup',
addressToConfirm: null,
bestViewedByTheseBounds: null,
location:{
name: null,
address: null,
position: null
}
}
}
//inside of the component:
data: function (){
return initialState();
}
methods:{
resetWindow: function (){
Object.assign(this.$data, initialState());
}
}
Caution, Object.assign(this.$data, this.$options.data()) does not
bind the context into data().
So use this:
Object.assign(this.$data, this.$options.data.apply(this))
cc this answer was originally here
To reset component data in a current component instance you can try this:
Object.assign(this.$data, this.$options.data())
Privately I have abstract modal component which utilizes slots to fill various parts of the dialog. When customized modal wraps that abstract modal the data referred in slots belongs to parent
component scope. Here is option of the abstract modal which resets data every time the customized modal is shown (ES2015 code):
watch: {
show (value) { // this is prop's watch
if(value) {
Object.assign(this.$parent.$data, this.$parent.$options.data())
}
}
}
You can fine tune your modal implementation of course - above may be also executed in some cancel hook.
Bear in mind that mutation of $parent options from child is not recommended, however I think it may be justified if parent component is just customizing the abstract modal and nothing more.
If you are annoyed by the warnings, this is a different method:
const initialData = () => ({})
export default {
data() {
return initialData();
},
methods: {
resetData(){
const data = initialData()
Object.keys(data).forEach(k => this[k] = data[k])
}
}
}
No need to mess with $data.
I had to reset the data to original state inside of a child component, this is what worked for me:
Parent component, calling child component's method:
<button #click="$refs.childComponent.clearAllData()">Clear All</button >
<child-component ref='childComponent></child-component>
Child component:
defining data in an outside function,
referencing data object by the defined function
defining the clearallData() method that is to be called upon by the
parent component
function initialState() {
return {
someDataParameters : '',
someMoreDataParameters: ''
}
}
export default {
data() {
return initialState();
},
methods: {
clearAllData() {
Object.assign(this.$data, initialState());
},
There are three ways to reset component state:
Define key attribute and change that
Define v-if attribute and switch it to false to unmount the component from DOM and then after nextTick switch it back to true
Reference internal method of component that will do the reset
Personally, I think the first option is the clearest one because you control the component only via Props in a declarative way. You can use destroyed hook to detect when the component got unmount and clear anything you need to.
The only advance of third approach is, you can do a partial state reset, where your method only resets some parts of the state but preserves others.
Here is an example with all the options and how to use them:
https://jsbin.com/yutejoreki/edit?html,js,output

Categories

Resources