I have a ES6 class that I use to hold and manage datas:
import DataManager from "./components/data-manager.js";
export default {
...
data() {
return {
dataModel: null
}
},
beforeMount() {
this.dataModel = new DataManager();
},
computed: {
datas() {
return this.dataModel.myProperty
}
},
...
}
But it appears that there is a reactivity issue and any changes made into that dataModel does not trigger the re-rendering of the view.
How can I make the properties of my class reactive ?
Thanks,
Edit: #acdcjunior The Class looks like this, but I realize that some mutation occurs... would that be the issue ? Anyway if thats not good practice to make a whole ES6+ class reactive, I'll get ride of that and go for a plain Object, or even Vuex store. Any simple suggestion ?
class DataManager {
constructor() {
this.professionalsList = [];
this.eventsList = [];
this.attendeesList = [];
}
// import datas
importDatas({
professionalsList,
eventsList,
attendeesList
}) {
this.professionalsList = this.parsePros(professionalsList)
...
}
parsePros(list) {
return list.map( item => { ... })
}
...
}
Edit #2: So the issue was indeed some mutations that occures in one of the objects I was trying to bind.
You need to store the same data structure in data().dataModel as returned from DataManager() and change every property in methods. When you setting new dynamic property in data object they can't be reactive. You can try Vue.set(), but it's a bad practice.
Related
There's a similar question here, but for Vue2. The solution, use Vue.set, is not valid in Vue3. I'm getting a lot of results about Vue2, but nothing about Vue3 for this yet.
There's a lot of fanciness going on in the code, but it's really quite moot. My object lives in a state in the vuex store. It has a non-enumerable property that is a function that adds records to itself like so:
class myObject extends Object {
constructor(){
this.add = function(id){
isReactive(this) //=== true, because vuex
this[id] = new myOtherObject(data);
}
}
}
Question:
Is there a way to force the reactive wrapping this object to know it's had these properties added?
There are other work arounds to my problem, one of which I've implemented, and is basically to include some other thing in the computed so it knows it's time to update itself. Luckily, I have a non-enumerable length property on my object that is incremented when a property is added that works perfectly:
const record = computed(() => {
store.state.myData.myObject.length; //<- literally just an incremented ref
return store.state.myData.myObject; //<- the problem child
});
But this is weird and hacky, and feels very anti-pattern.
Example code:
class myOtherObject extends Object {};
class myObject extends Object {
constructor(){
super();
this.add = function(id){
console.log(`Added: ${id}`);
this[id] = new myOtherObject();
}
}
}
const store = Vuex.createStore({
state: function(){
return {
myData: {
test1: new myObject()
}
}
}
});
const record = Vue.computed(() => {
console.log("This isn't happening when new props are added");
return store.state.myData.test1; //<- the problem child
});
for (var i = 0; i < 100; ++i){
store.state.myData.test1.add(i);
}
console.log(store.state.myData.test1);
<script src="https://unpkg.com/vue#3.2.30/dist/vue.global.js"></script>
<script src="https://unpkg.com/vuex#4.0.2/dist/vuex.global.js"></script>
I create an object from a class in the mounted event. I need to use this object throughout my component.
I've been using a data field to store the object (its not a JSON object, its a full on instantiated class).
Where is the best place to store this object? So I can use it throughout my component?
Based on your comment, it sounds like you're looking for ways to declare non-reactive data, scoped to each component instance.
Option 1: Use an attached property
Assign this.VARNAME = VALUE
Typically done in the created() hook, but can be just about anywhere in the component context
IntelliSense in IDEs may have trouble discovering this property (TypeScript will require type assertions/declarations)
Example:
export default {
created() {
this.myNonReactiveProp = new MyClass()
}
}
Option 2: Use a data property with Object.freeze()
Object.freeze() prevents the property from being reactive, but also make its completely readonly
Can be useful for static data
IntelliSense can detect this property (as it does for all data() properties)
Example:
export default {
data() {
return {
myNonReactiveProp: Object.freeze(new MyClass())
}
}
}
You can pass class between vue components via plugin:
https://v2.vuejs.org/v2/guide/plugins.html
Install your plugin globally, you should call the class like this, for example:
class myClass {
// ...
}
Vue.prototype.$myClass = new myClass;
OR in the component only:
<script>
class test {
}
export default {
data: () => ({
instance: new test
}),
mounted() {
console.log(this.instance)
}
}
</script>
I have a UserDialog component which leverages a part of the Vuex state-tree to determine whether it should display itself or not:
import { Component, Prop, Vue } from 'vue-property-decorator';
import { State, Getter, Mutation, Action, namespace } from 'vuex-class';
import { fk } from 'firemodel';
import { User } from '#/models/User';
const Users = namespace('users');
#Component({})
export default class UserDialog extends Vue {
#Prop() public id!: fk;
#Users.State public show: fk;
#Users.Getter public selectedUser: User;
#Users.Mutation public HIDE_USER_PROFILE: () => void;
public get showDialog() {
return this.show === undefined ? false : true;
}
}
From the parent component I am calling Vuex's commit('SHOW_USER_PROFILE', id) and thereby setting this ID it should update the UserDialog's show property accordingly.
I can see very clearly that the Vuex store has received the call to SHOW_USER_PROFILE and that indeed has updated the state in the state tree (this is through the Vue Developer plugin in the browser). But then when I switch over to the UserProfile component I see that it still has not received the state update.
Note: if I reload the page (aka, CMD-R) after having set the UserID I want to highlight, it reloads the components and because I'm using veux-persist, the ID is still set in the state tree. At this point the component DOES receive the correct state but when relying on the normal reactivity system it just doesn't work.
Can anyone help?
for additional context, here are a few more modules:
Store Definition::
export default new Vuex.Store<IRootState>({
modules: {
packages,
users,
searchCriteria,
snackbar
},
plugins: [FireModelPlugin, localStorage.plugin]
});
Users Mutations:
const mutations: MutationTree<IUsers> = {
selectUser(state, id: fk) {
state.selected = id;
},
SHOW_USER_PROFILE(state, id: fk) {
state.show = id;
},
HIDE_USER_PROFILE(state) {
state.show = undefined;
}
};
I have added a computed property to the UserDialog component above:
public get userId() {
return this.$store.state.users.show;
}
There was a thought that maybe this would be reactive whereas the #Users.State decorated show property was not. Unfortunately, they both perform exactly the same.
#Derek and I talked last night and realized that the cause of this problem was due to the state transitions to "undefined" which the current Reactive system does not handle (it should be fine when we get to Vue-NEXT with Object Proxies). The remaining code works just fine when I switch out the state transition from: undefined → string → undefined to null → string → undefined.
Many thanks to #Derek for spending the time.
In the example above you're directly calling the Vuex state store. When you do this from your component this is a one time get deale. The state store is not reactive and will never tell your computed property that it changed.
The correct way to get the reactivity you're looking for is to implement Vuex getters:
const store = new Vuex.Store({
state: {/*...*/},
getters: {
show(state) {
return state.show;
}
}
})
Then in your component:
computed: {
show() {
return this.$store.getters.show;
}
}
Read more about Vuex getters here: https://vuex.vuejs.org/guide/getters.html
I have a property selectedOption on the state of my Aurelia Store, which can be changed via actions. I want to observe/subscribe to any changes to this property on the state. My problem is the subscription within the BindingEngine doesn't work because every time you change the state, you create a new copy of the state, therefore the subscription no longer works.
Here is my example:
import { Disposable, BindingEngine, autoinject } from "aurelia-framework";
import { connectTo, dispatchify } from "aurelia-store";
#autoinject()
#connectTo()
export class Holiday {
subscription: Disposable;
state: any;
constructor(private bindingEngine: BindingEngine) {
}
async updateState()
{
await dispatchify(changeSelectedOption)();
}
attached() {
this.subscription = this.bindingEngine
.propertyObserver(this.state, 'selectedOption')
.subscribe((newValue, oldValue) => {
console.log("something has changed!")
});
}
}
export class State {
selectedOption: number = 0;
}
export const changeSelectedOption = (state: State) => {
let updatedState = { ...state };
updatedState.selectedOption++;
return updatedState;
}
store.registerAction("changeSelectedOption", changeSelectedOption);
The first time, my subscription will work and the console will log "something has changed!" as the state is the same object, but it won't work after.
Another solution I could use would be to have a computed property like so:
#computedFrom("state.selectedOption")
get selectedOptionChanged()
{
return console.log("something has changed!");
}
This is a hack, and this computed won't ever be triggered as it is not bound to anything in the HTML.
For context, I want to trigger a server call every time the selectedOption property changes.
What can I do to receive all updates from the property on the state?
The thing here is that the state observable exposed by the Store is a RxJS stream. So with the advent of the new "multi-selector" feature for connectTo you could create two bindings. By implementing a hook called selectorKey Changed, in your sample selectedOptionChanged it would get called on every change of said property.
#connectTo({
selector: {
state: (store) => store.state, // the complete state if you need
selectedOption: (store) => store.state.pluck("selectedOption")
}
})
class MyVM {
...
selectedOptionChanged(newState, oldState) {
// notification about new state
}
}
Instead of store.state.pluck("selectedOption") you can also experiment with additional conditions when to notify about changes like adding distinctUntilChanged etc.
Read more about multi-selectors in the updated docs.
Alternatively if you don't want to use the connectTo decorator, simply use the state property and create another subscription
What are the best practices/patterns make realm a reactive datasource in a react native app? Especially for presentational and container components pattern?
Here is an example which I'd like to make reactive: Realm with React Native
The docs on auto-updates/change-events are a bit thin and the official example does not make use of this feature (to my knowledge).
You can make your example reactive by subscribing to events and updating the ui when you receive a change event. Right now events are only sent when write transactions are committed, but finer grained change events will be added in the future. For now you could add the following constructor to update the ui on changes:
constructor(props) {
super(props);
this.realm = new Realm({schema:[dogSchema]})
this.realm.addListener('change', () => {
this.forceUpdate()
});
}
You need to hold onto a Realm instance to keep the notifications alive, and you can use this Realm instance throughout the rest of the component.
Instead of calling forceUpdate, you could instead set the component's state or props within the event listener to trigger the refresh, like so:
constructor(props) {
super(props);
this.realm = new Realm({schema:[dogSchema]})
this.state = {...}; // Initial state of component.
this.realm.addListener('change', () => {
this.setState({...}); // Update state instead of using this.forceUpdate()
});
}
I think #Ari gave me a good answer for redux folks as i was also struggling. I'm not sure if it's immutable enough but it works!
I'm simpliy dispatching getVehicles action inside addListener and it just works!
Below is UI component whose constructor function makes the magic!
//- importing my realm schema
import realm from '../../db/models';
//- Importing my action
import { getVehicles } from './../../actions/vehicle';
#connect((store) => {
return {
vehicle: store.vehicle.vehicles
}
})
export default class Devices extends Component {
constructor(props) {
super(props);
realm.addListener('change', () => {
props.dispatch(getVehicles());
});
}
}
Below is db/models file used up there in the constructor.
import Realm from 'realm';
class VehicleSchema {};
VehicleSchema = {
name: 'vehicleInfo',
properties: {
vehicleName: 'string',
vehicleNumber: 'string',
vehiclePassword: 'string',
vehiclePasswordTrigger: 'bool',
vehicleType: 'string',
vehiclePicture: { type: 'data', optional: true }
}
};
export default new Realm({schema: [VehicleSchema]});
Below is the actions/vehicle file, which gets dispatched in the constructor above.
import { queryVehicle } from './../db/queryVehicle';
export function getVehicles() {
const vehicles = queryVehicle();
return function(dispatch) {
dispatch({type: "GOT_VEHICLES", payload: vehicles});
}
}
Below is my queryVehicle function that does the querying called in action file above.
import vehicleModel from './models';
const queryVehicle = (queryInfo="vehicleInfo", filter='') => {
const objects = vehicleModel.objects(queryInfo);
if(filter.length === 0) return objects;
let results = objects.filtered(filter);
return results;
};
export { queryVehicle };
disclaimer I don't know if this code looks immutable enough, or following good redux practice cause i'm just starting out with redux so give me some comments advising if i'm doing something wrong.
I'll also guess reducer implementation wouldn't matter much in this here.
Recently ran into an issue with Realm ListView auto-updating. When the ListView rows have varied heights, you can get overlaps on rows in the UI. The below was the only way I could get the ListView to re-render without causing UI overlaps. It seems a bit "dirty" to me, so if there is a better way, I welcome the input. But this is working perfectly so far; incase anyone else runs into this issue.
Basically it just wipes the dataSource, then inserts it again using the setState callback when there are insertions or deletions, but modifications simply roll through and auto-update.
let feed = this.props.store.feed;
feed.addListener((name, changes) => {
if (changes.insertions.length || changes.deletions.length) {
this.setState({dataSource: this.ds.cloneWithRows([])},
() => this.setState({dataSource: this.ds.cloneWithRows(feed)})
);
} else {
this.setState({dataSource: this.ds.cloneWithRows(feed)});
}
});