Vue.js list not updating despite array change via assignment - javascript

I'm new to vue.js but I'm trying to display a list of options from an underlying array. However, when the underlying array is changed the list does not update/re-render. I am assigning the new array directly to the vue instance data value (not using splice or assigning via index) and in the console if I try and print out the underlying data (vm.clarifyings) that is updated, it is just the re-rendering that is not working. Even using vm.clarifyings.push(object) does not update the view in the browser.
Vue instance code:
var vm = new Vue({
delimiters: ['$[', '$]'], //change delimiters to allow django integration
el: '#vue_app',
data: {
title: init_info.title, //page title
active: 'presentations',
navClass: 'nav-item nav-link', //necessary for navbar rendering
features: features,
question: '',
response: '',
feature_id: init_info.opening,
last_feature: '',
clarif_id: '',
clarifyings: [],
show_clarifying: false,
},
Relevant method update:
fetch(url)
.then(response => response.json())
.then(function (data) {
// Type out question and response
typeWriter(data.question, 'question');
typeWriter(data.answer, 'response');
// Save selected option and disable previous selected option
option_disable(vm.last_feature);
vm.last_feature = option_ref;
// Show clarifying questions
vm.clarifyings = data.clarifyings;
if (vm.clarifyings.length){
vm.show_clarifying = true;
}
else {
vm.show_clarifying = false;
}
}
All of this executes normally it is simply the re-rendering that isn't working. If I specify the array when I initialize the Vue instance it renders properly it simply does not update.
HTML code:
<select class="selectpicker" data-live-search="true" v-model="clarif_id">
<option v-for="question in clarifyings">$[question.id$] - $[question.name$]</option>
</select>

Vuejs has a bad reactivity for arrays. See the official doc : https://v2.vuejs.org/v2/guide/reactivity.html#For-Arrays
Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
You can use Vue.set(vm.clarifyings, indexOfItem, newValue) to overcome this problem. (Instead of vm.clarifyings.push(object))

The problem appeared to be a result of interference with bootstrap-selectpicker, it was fixed by using nextTick functionality in vue like so:
if (feature_asked) {
vm.clarifyings = data.clarifyings;
if (vm.clarifyings.length) {
vm.show_clarifying = true;
vm.$nextTick(function () {
$("#clarifying_qs").selectpicker("refresh");
});

Related

Axios: Previous response data being assigned to variable

I am creating a frontend to a patient booking system with Vue.js, which is simply a dynamic web form. The user first selects a type of treatment, then the practitioner they want to see, and finally the appointment time. All data is obtained via RESTful API calls using axios.
The available options in each form field are filtered using the previous choice. For example, the user will only be presented with the available times of their selected practitioner, and the practitioner can only be selected from the group of practitioners who can perform the chosen treatment.
Filtering the practitioners based on the selected treatment works just fine.
However, filtering the appointments based on the selected practitioner does not work -- it's out of sync: the appointments are loaded for the previously selected practitioner. I have checked the backend, which is fine, and the API calls are in-sync (i.e. the person_id matches the id of the newly selected practitioner).
What is causing this problem and how do I fix it?
Here is the Vue.js code that performs this filtering:
var app = new Vue({
el: '#app',
data: {
appointments: [],
practitionerId: 0,
practitioners: [],
treatmentId: 0,
treatments: [],
},
mounted: function () {
axios.get('/api/treatments')
.then(response => this.treatments = response.data);
},
watch: {
// filter available practitioners by the selected treatment
treatmentId: function () {
// get the allowed role ids for the selected treatment
var allowedRoleIds = '';
const allowedRoles = this.treatments[this.treatmentId - 1]['allowed_roles'];
for (var i = 0; i < allowedRoles.length; i++) {
allowedRoleIds += allowedRoles[i]['id'];
if (i + 1 < allowedRoles.length) {
allowedRoleIds += ',';
}
}
// load the practitioners using the allowed role ids
axios.get('/api/people?role_ids=' + allowedRoleIds)
.then(response => this.practitioners = response.data);
},
// filter the available appointments by the selected practitioner
practitionerId: function () {
axios.get('/api/appointments?person_id=' + this.practitionerId)
// ERROR!!! This is out of sync.
.then(response => this.appointments = response.data);
}
}
});
The problem can be resolved by adding a watcher to the appointments variable.
All I needed to do was add the following code within watch: { ... }:
appointments: function () {
// now it works -- even without any function body
}
This seems really odd to me. I should not need to create a watcher for a variable in order to have that variable updated in the function body of another watcher.
I have either missed something in the Vue.js documentation about watchers or this is a bug. If someone can shed some light on this in the comments that would be great!
You need to refresh practitionerId after fetching people from RESTful API.
For example, in treatmentId watch:
axios.get('/api/people?role_ids=' + allowedRoleIds).then(response => {
this.practitioners = response.data;
// refresh practitionerId whenever fetch new people
const selectedPractitionerId = this.practitionerId;
this.practitionerId = 0;
// if selected practitioner exists in new people
practitioners.forEach(p => {
if (p.id == selectedPractitionerId) {
this.practitionerId = p.id;
}
}) // you can omit this search if you force the user to select new practitioner whenever they change treatment
});

Pass value from map to state

//It's working now - updated code
I'm working on my own autocomplete component because I have problem with passing firebase data to a ready one.
The whole mechanism is working good but I have problem with passing values after getting user input
I'm setting initial state with those values
const INITIAL_STATE = {
allChars: [],
suggestions: [],
value: ""
};
Then in autocomplete class i'm loading all users from database
loadData(){
let self = this;
let characters = firebase.firestore().collection("users");
characters.get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
let document = doc.data();
self.setState(({allChars})=>({
allChars: [
...allChars,
document
]
}))
});
});
}
Here is my getSuggestions function. It is firing on input change
getSuggestions = event => {
const {value, suggestions} = event.target;
this.setState({
value: value,
suggestions: []
})
let suggest = [];
this.state.allChars.map((allChars) => {
if(value.length > 1 && allChars.name.toLowerCase().includes(value.toLowerCase())){
suggest.push (
allChars.name
);
}
})
this.setState({
suggestions: suggest
})
}
In render I just put {sugestions}
But in {suggestions} I get rendered only one name.
one
But when I console.log it - I get two names
two
There should be two.
I tried to set state in this function like in loadData(), but I still get only one value.
Is there other way to get both values into DOM
Full code can be found here: https://github.com/Ilierette/react-planner/blob/master/src/component/elements/Autocomplete.js
I think the reason you are just seeing one element each time your components re-render is that in your map function on your allChars array, when you want to update the suggestions in your state, you are setting just the name each time as a new array while you should update the existing array in your state, so your code should be:
this.setState({
suggestions: [...this.state.suggestions, allChars.name]
})

react-draft-wysiwyg update editorstate from dynamically created editor

I'm currently working on a project which involves using multiple wysiwyg editors. I have previously used react-draft in the same project but has always been used with static elements eg, each editor is fixed.
In my case, my editors are created on the fly, (min 1, max 15) editors. I'm rendering these into my containers using map() with constructed object each time. Allowing the user to click + or - buttons to create / remove a editor.
for example to create a new editor into, i push to then map over the components array which looks something like the below:
components: [
{
id:1,
type: 'default',
contentValue: [
title: 'content-block',
value: null,
editorState: EditorState.CreateEmpty(),
]
}
]
I am able to render multiple editors just fine and createEmpty ediorstates. My issue is when i try to update the contents editor state.
Usually to update a single editor id use:
onEditorStateChange = editorState => {
this.setState({
editorstate,
})
}
However, given the fact my editors are dynamically rendered, i have the editor state isolated within the "Components" array. So i've tried the following which did not work:
In Render
this.state.components.map((obj) => {
return (
<Editor
editorState={obj.contentValue.editorState}
onEditorStateChange={(e) => this.onEditorStateChange(e, obj.id)}
/>
);
}
onEditorStateChange
onEditorStateChange(e, id){
const { components } = this.state;
const x = { components };
for (const i in x){
if(x[i].id ==== id){
x[i].contentValue.editorState = e;
}
}
this.setState({components: x})
}
Upon debugging, the "setState" does get called in the above snippet, and it does enter the if statement, but no values are set in my editorState.
I'm happy for alternative ways to be suggested, as long as this will work with dynamically rendered components.
I require this value to be set, as i will be converting to HTML / string and using the content to save to a database.
I hope i have explained this well, if not please let me know and ill be happy to provide further information / snippets.
Thank you in advance.
Okay, i figured out a solution.
inside my onEditorStateChange() i update the value with the parameter (e) which was initally passed in. Using draftToHtml i convert it to raw and pass it to set state as shown below:
onEditorStateChange(e, id) {
const { components } = this.state;
console.log(e);
const x = components;
for (const i in x) {
if (x[i].id === id) {
x[i].contentValue.editorState = e;
x[i].value = draftToHtml(convertToRaw(e.getCurrentContent()));
//console.log(x[i]);
}
}
this.setState({
components: x,
});
}
This gives me the a HTML value which i can now convert to string and save to the database.
Hope this helps someone else with the same issue :)

Reverse JSON data in Vue.JS using reverse()

I need to reverse the order of a JSON feed that is being used within a Vue.JS project.
In other frameworks the reverse() function has done the job. I have tried adding to the Vue.JS script but no joy yet.
Any ideas on other syntax I could try?
<script type="text/javascript">
new Vue({
el: '#vueapp',
data: {
json: null
},
created: function () {
var _this = this;
$.getJSON('https://www.example.com/myfeed.json', function (json) {
_this.json.reverse() = json;
});
}
});
</script>
So the last item in the JSON (the house on the side of the mountain in the example below), will display first when the page loads the JSON data.
https://jsfiddle.net/5kt9jurc/3/
Some Googling seems to show reverse() no longer works with Vue2 and is deprecated.
You can reverse an array. Take a look at my fiddle. I replaced getJSON with fetch to use promises. Also you don't need to do var _this = this;
It looks a bit weird. Since in Vue.js properties are reactive you can just set a value.
Here is a code:
const app = new Vue({
el: '#app',
data: {
items: []
},
created: function() {
fetch('https://api.jsonbin.io/b/5b02b2bbc83f6d4cc7349a7b')
.then(resp => resp.json())
.then(items => {
this.items = items.reverse()
})
}
});
I think what you want is something like this:
_this.json = json.reverse();
That is, assuming json is an already parsed json array. Otherwise, first parse the json string:
const array = JSON.parse(jsonString);
this.data = array.reverse();
The way you are doing you are trying to assign a value to the result of an expression, which doesn't make much sense (right side values can't be assigned to).

Filter data using a search query? Wordpress JSON object + VueJS2

EDIT: Live Code Editor added: https://ide.c9.io/dosstx/wordpress
I am trying to filter a Wordpress JSON data object using VueJS2 and the Wordpress REST API (I have a custom post type in my real world example).
I'm having trouble with the wiring and getting the table to filter based on the search terms that are typed into the search box.
Without the search function, everything works fine, but once I try to filter using a searchterm, nothing happens -- no error in console.
I have my Vue instance like so:
var vm = new Vue({
el: '#app',
data: {
searchTerm: '',
posts: []
},
computed: {
filteredItems: function(){
return this.posts.filter(function(post) {
return this.post.searchTerm; //i believe this line is the culprit
});
}
},
created: function(){
$.get('mylocalhost/wp-json/wp/v2/products/' + '?_embed=true')
.done(function(data) {
vm.posts = data;
});
}
});
My HTML:
<div id="app">
<form>
<input type="text" v-model="searchTerm">
</form>
And further down my HTML....:
<tr v-for="post in filteredItems">
<td>{{post.title.rendered}}</td>
...snip ...
</div>
Any clues on how to fix would be greatly appreciated.
You aren't using the filter method correctly.
From the MDN Docs for the filter method:
filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true.
The callback passed to filter should return a Boolean value to determine whether or not to include the element of the array in the filtered array.
In your case, I'm assuming your post objects have some property (say content) you want to search and that you want to only include posts with content that contain the search term. So you can do something like this:
computed: {
filteredItems: function() {
return this.posts.filter(function(post) {
return post.content.indexOf(this.searchTerm) != -1;
});
}
},

Categories

Resources