How to not display certain index in an Array without filter in Vue3? - vuejs2

In vue2 if you wanted to not display a certain index in an array you could do something like:
this.tasks = this.tasks.filter((task) => task.id !== 1)
Where it would be used show all values in the 'tasks' array except for task that has an id=1.
Now in vue3 using you would have tasks as a ref value: const tasks = ref([])
How would you do the vue2 equivalent in vue3?

Similar, for ref You use property value, try with :
tasks.value = tasks.value.filter((task) => task.id !== 1)

Related

VueJS 3 Prevent re-computation if computed value hasn't changed

I have a chain of computed values like this:
var lt = reactive({
scrollTop: shallowRef(0),
row: computed(() => Math.floor(lt.scrollTop / lt.rowHeight)),
records: computed(() => props.collection.filter((x, i) => {
console.log(lt.row);
return i > row && i < row + 30;
}))
})
scrollTop is tied to a scroll listener. This setup works, but I notice that the records computed is getting recalculated even though row hasn't actually changed. I understand this happens because scrollTop changes, and triggers the proxies all the way down the chain.
Is there a way to ensure a computed doesn't change unless there's an actual value change in a direct contributor var? If not, is there a better pattern for the data here that would solve this?

Vuex model update won't reload computed property

I have the following component to quickly configure stops on a delivery/pickup route and how many items are picked up and dropped
and this is the data model, note the 2 is the one next to 'a' on the previous image.
If a click the + or - button, in the first item, it behaves as expected,
But second item doesn't work as expected
I've already checke a couple of posts on object property update likes this ones
Is it possible to mutate properties from an arbitrarily nested child component in vue.js without having a chain of events in the entire hierarchy?
https://forum.vuejs.org/t/nested-props-mutations-hell-internet-need-clarification/99346
https://forum.vuejs.org/t/is-mutating-object-props-bad-practice/17448
among others, and came up with this code:
ADD_ITEM_TO_SELECTED_STOP(state, payload) {
let count = state.selectedStop.categories[payload.catIndex].items[payload.itemIndex].count;
const selectedCat = state.selectedStop.categories[payload.catIndex];
const currentItem = selectedCat.items[payload.itemIndex];
currentItem.count = count + 1;
selectedCat.items[payload.itemIndex] = currentItem;
Vue.set(state.selectedStop.categories, payload.catIndex, selectedCat);
},
and as the button event:
addToItem(item) {
this.$store.dispatch("addItemToSelectedStop", {
catIndex: item.catIndex,
itemIndex: item.itemIndex
})
},
And finally my computed property code:
items() {
let finalArray = [];
this.selectedStop.categories.forEach(
(cat, catIndex) => {
let selected = cat.items.filter((item) => item.count > 0 );
if (selected.length > 0) {
//here we add the catIndex and itemIndex to have it calling the rigth shit
selected = selected.map(val => {
let itemIndex = cat.items.findIndex( itemToFind => itemToFind.id === val.id);
return {
...val,
catIndex: catIndex,
itemIndex: itemIndex,
}})
finalArray = finalArray.concat(selected);
}
});
return finalArray;
}
What confuses me the most is that I have almost the same code in another component, and there it's working as expected, and although the model is changed, the computed property is only recalculated on the first item,
After reading this gist and taking a look again at the posts describing this kind of issue, I decided to give it a try and just make a copy of the whole stored object not just the property, update it, then set it back on vuex using Vue.set, and that did the trick, everything is now working as expected, this is my final store method.
ADD_ITEM_TO_SELECTED_STOP(state, payload) {
let selectedLocalStop = JSON.parse(JSON.stringify(state.selectedStop));
let count = selectedLocalStop.categories[payload.catIndex].items[payload.itemIndex].count;
selectedLocalStop.categories[payload.catIndex].items[payload.itemIndex].count = count + 1;
Vue.set(state,"selectedStop", selectedLocalStop );
//Now we search for this step on the main list
const stepIndex = state.stops.findIndex(val => val.id === selectedLocalStop.id);
Vue.set(state.stops,stepIndex, selectedLocalStop );
},
I had to add the last bit after updating the whole object, because, originally, the array items were updated when the selected item was changed, I guess some sort of reference, but with the object creation, that relationship no longer works "automatic" so I need to update the array by hand

Vuexfire bindFirebaseRef with pagination infinite scroll

Question: How can I add pagination (infinite scroll) to my binded Firestore VuexFire reference without re-querying previously retrieved (and binded) results?
Background:
I am currently using VuexFire firestore binding to fill a timeline for most upvoted posts, as an action, in my Vuex store like this:
fillTimeLine: firebaseAction(
({ bindFirebaseRef }) => {
bindFirebaseRef(
'timelineResults',
db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.limit(30)
)
})
This will retrieve the top 30 highest rated posts in my firestore database to my vuex state variable timelineResults.
To add pagination I have found a non-VuexFire example like this:
How to paginate or infinite scroll by number of items in firestore?
var first = db.collection("....").orderBy("price", "desc").limitTo(20);
return first.get().then(function (documentSnapshots) {
// Get the last visible document
var lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
var next = db.collection("....")
.orderBy("price", "desc")
.startAfter(lastVisible)
.limit(20);
});
Is there a way to combine the two examples and append results to a binded reference?
You could create a more generic action, just like this:
bindRef: firestoreAction(({ bindFirestoreRef }, { name, ref }) => {
bindFirestoreRef(name, ref);
}),
And then using it like:
this.bindRef({
name: 'timelineResults',
ref: db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.limit(30),
});
There you can change the ref according to your needs. In this case, when you detect the scroll limit:
// lastVisible: using the array position from the previous binding
// since with vuex's bound data you cannot get the snapshots
this.bindRef({
name: 'timelineResults',
ref: db
.collection('POSTS')
.orderBy('combined_vote_score', 'desc')
.startAfter(lastVisible)
.limit(20),
});

React-Native + Redux: Random number of form fields

I am a newbie to react-native, redux and saga and have run into a use case that I have not been able to find a solution for. I understand how to map state to properties and pass around the state between action, reducer and saga. This makes sense to me so far. This is where things seem to get dicey. I have a form that requires a variable number of form fields at any given time depending upon what is returned from the database.
As an example, let's say I have a structure like this:
{
name: ‘’,
vehicleMake: ‘’,
vehicleModel: ‘’,
carLotCity: ‘’,
carLotState: ‘’,
carLotZipCode: ‘’,
localPartsManufacturers: [{name: ‘’, address: ‘’, zipCode}]
}
Everything from name to carLotZipCode would only require one text field, however, the localPartsManufacturers array could represent any number of object that each would need their own set of text fields per each object. How would I account for this with redux as far as mapping the fields to the state and mapping the state to the properties? I am confused about how to begin with this scenario. I understand how to project mapping when the fields are fixed.
I would keep the data as it is coming from the backend. That way you'll avoid normalizing it. I think we just have to be smarter when rendering the fields. Here's what I'm suggesting:
function onTextFieldChange(name, index) {
// either name = `name`, `vehicleMake`, ...
// or
// name = `localPartsManufacturers` and `index` = 0
}
function createTextField(name, index) {
return <input
type='text'
name={ name }
onChange={ () => onTextFieldChange(name, index) } />;
}
function Form({ fields }) {
return (
<div>
{
Object.keys(fields).reduce((allFields, fieldName) => {
const field = fields[fieldName];
if (Array.isArray(field)) {
allFields = allFields.concat(field.map(createTextField));
} else {
allFields.push(createTextField(fieldName));
}
return allFields;
}, [])
}
</div>
);
}
Form receives all the data as you have it in the store. Then we check if the field is an array. If it is an array we loop over the fields inside and generate inputs same as the other properties createTextField. The tricky part here is how to update the data in the store. Notice that we are passing an index when the text field data is changed. In the reducer we have to write something like:
case FIELD_UPDATED:
const { name, index, value } = event.payload;
if (typeof index !== 'undefined') {
state[name][index] = value;
} else {
state[name] = value;
}
return state;
There is nothing preventing you from keeping a list, map, set or any other object in Redux.
The only thing remaining then, is how you map the state to your props, and how you use them. Instead of mapping a single element from the collection to a prop, you map the entire collection to a single prop, and then iterate over the collection in your render method.
In the action you can pass a new collection back, which is comprised of the form fields making up the parts list. Then, your reducer will replace the collection itself.
Or, upon changing an element in the part collection, you can send an action with its id, find it in the collection in the reducer and replace the element that was changed / add the new one / remove the deleted one.

Is there a better way to share data between dynamically loaded components?

I recently built a small application with Vue.js and Express.js. There are few components needed to be prepared by the servers, e.g., the combobox for Article.Category and Article.User. The options for these 2 components needed to be rendered from server. I use <component /> as the placeholder for these 2 components in the article edit form:
<component v-bind:is="user_selection_component"></component>
<component v-bind:is="category_selection_component"></component>
I use the template string for initialising the components, the template string result.data.template is passed by server:
let org_data = original_store;
let new_data = () => {
org_data['remote_options'] = result.data.remote_options;
//if there is any default value, then assign the value to field referred by "model_name"
if(model_name && result.data.preset_value){
let previous_value = $shared.index(org_data, model_name);
if(!previous_value){
$shared.index(org_data, model_name, result.data.preset_value);
}
}
return org_data;
}
var default_cb = ()=>{console.info('['+model_name+'].default_cb()')};
let TempComponent = {
template: result.data.template,
methods: component_ref.methods,
data: new_data,
mounted: cb !== null ? cb.bind(this, org_data) : default_cb.bind(this)
};
app[mount_component] = TempComponent;
Here is the problem, the data method returns a new Observable store for the dynamically loaded components, they don't share the same store object with the parent component, which is the article edit from. Hence, if I want to modify the category field value or user field value, I have to let the callback function cb to accept the store objects of these 2 dynamically loaded components. Otherwise, from the parent component, I could not modify the values in these 2 components.
So I came up with a temporary workaround, I passed the setter method as the callback function to these dynamically loaded functions:
let set_user_id = null;
let set_cate_id = null;
(org_store) => { set_user_id = (new_id) => { org_store.form.user_id = new_id; }}
(org_store) => { set_cate_id = (new_id) => {org_store.form.category_id = new_id; }}
After I load other components or anytime I want to set the category/user value, I can just call set_user_id($new_user_id) or set_cate_id($new_category_id);
I don't like this work around at all. I tried to use the event handler to emit the new values into these 2 components. But I couldn't access these 2 dynamically loaded component via $ref. Is there a better way to let data be shared between dynamically loaded components? Thanks.
If your components will accept props, you can localize your event bus, which is a little nicer than having a global. The parent component creates the bus as a data item:
data() {
...
bus: new Vue()
}
The components accept it as a prop:
<component v-bind:is="user_selection_component" :bus="bus"></component>
<component v-bind:is="category_selection_component" :bus="bus"></component>
and you use it as in your answer, except referring to this.bus instead of just bus.
I don't think what I have now is the best solution. After consulting with other people, I took event bus as the better solution. So I modified my code as:
In my init component:
let org_data = original_store;
let new_data = () => {
org_data['remote_options'] = result.data.remote_options;
//if there is any default value, then assign the value to field referred by "model_name"
if(model_name && result.data.preset_value){
let previous_value = $shared.index(org_data, model_name);
if(!previous_value){
$shared.index(org_data, model_name, result.data.preset_value);
}
}
return org_data;
}
var default_cb = () => { console.info('['+model_name+'].default_cb()') };
let TempComponent = {
template: result.data.template,
methods: component_ref.methods,
data: new_data,
mounted: cb !== null ? cb.bind(this, org_data) : default_cb.bind(this)
};
bus.$on('set.' + model_name, function(value){
$shared.index(org_data, model_name, value);
});
The difference is here:
bus.$on('set.' + model_name, function(value){
$shared.index(org_data, model_name, value);
});
The bus is the common event bus created by Vue():
let bus = new Vue()
From the parent component, I can just use this event bus to emit the event:
bus.$emit('set.form.user_id', this.form.user_id);
I do feel better after changing to this solution. But I still appreciate if there is an even better way. Thanks.