MobX - observable not updating passed props? - mobx

When using MobX with React, I have 2 components. From the parent I send a prop to the child component like this:
import { computed } from 'mobx'
import { observer } from 'mobx-react'
#observer
class Parent extends React.Component {
#computed get user() {
const { gamer } = this.props;
}
render () {
return <div><Child user={this.user} /></div>
}
}
Child component:
import { observable } from 'mobx'
import { observer } from 'mobx-react'
#observer
class Child extends React.Component {
#observable owner = this.props.user;
render () {
return <div>{this.owner.name}</div>
}
}
The first time I run this with userX passed, the child shows the correct userX owner name, accessed via the #observable owner. The issue is the second time I run this with a different user passed userY, the child still shows userX even though the prop passed to it is correctly userY when I log it.
So the passed prop is different per user (as it should be), but the observable stays "locked" on the first user that was passed. Any idea why the observable isn't updating its value to the passed this.props.user?
Update:
So I tried #computed like this:
#computed get owner() {
return this.props.user;
}
but still the same issue. The only way I can seem to access the correct user, only in the render statement and directly from the passed prop as opposed to having mobx assign the prop value and read it from mobx observable/computed:
render() {
console.log(this.owner.name); // shows old data (even w/ observable or computed returning the passed prop)
console.log(this.props.user.name); // shows new data correctly without mobx
I just don't understand why the #observable or #computed don't return the correct new data. Is there anyway to have mobx correctly return the latest passed prop so the first console log works?

I think that we you do #observable owner = this.props.user, you do not create a reference to the original observable, but rather you create a new observable whose initial value will be the same as of the orginal one.
The solution (as it seems you already found) is to use the prop value directly in the Child component:
#observer
class Child extends React.Component {
render () {
return <div>{this.props.user.name}</div>
}
}
If you don't wanna do this, perhaps you can take a look at cloning* the observable using createViewModel from the mobx-utils package:
import { computed } from 'mobx'
import { observer } from 'mobx-react'
import { createViewModel } from 'mobx-utils'
#observer
class Child extends React.Component {
owner = createViewModel(this.props.user);
render () {
return <div>{this.owner.name}</div>
}
}
* Note: well, it is not exactly cloning, but the changes to user will get reflected in the owner object as well. Check the docs for more info.

Related

how to manage component mount and unmount to update state values?

I would like to know how to manage state property when the component mounts and unmounts.
I have a lot of different components in my application to maintain the application flow. I know about function componentdidmount and componentWillUnmount. and I also tried the solution about _isMounted=true on componentdidmount function and check _isMounted properties value when I update setState and then update _isMounted=false on componentWillUnmount function.
but this won't work when more two components come in the picture.
For example following links:
https://www.robinwieruch.de/react-warning-cant-call-setstate-on-an-unmounted-component/
Is there a way to check if the react component is unmounted?
as per the example, I have made a common class which will update the value of a component in setMounted function and will return value in getMounted function to validate component is mounted or not. These methods work correctly on a single screen when I call another screen from a stack and update some values then comes back on the previous page and refresh page it will ismount=false.
class Mount {
isMounted=false;
getMounted=()=>{
return isMounted;
}
setMounted=mounted=>{
isMounted=mounted;
}
}
var mount=new Mount();
export default mount;
class example extends component{
componentDidMount=async()=>{
mount.setMounted(true);
await this.loadScreen();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
async() => {
await this.loadScreen();
}
);
}
loadScreen=async()=>{
//some other stuff
if(mount.getMounted()){//second time value is false
this.setState({value:'value'});
}
}
componentWillUnmount() {
mount.setMounted(false);
}
//renderview where i call example2 on buttonclick
}
class example2 extends component{
componentDidMount=async()=>{
mount.setMounted(true);
await this.loadScreen();
}
loadScreen=async()=>{
//some other stuff
if(mount.getMounted()){
this.setState({value:'value'});
this.props.navigation.goBack();
}
}
componentWillUnmount() {
mount.setMounted(false);
this.willFocusSubscription.remove();
}
}
It was showing following warning before using mount functions:
Can't perform a React state update on an unmounted component
You are creating only a single instance of your Mount class that is exported and shared across every instance of every component. You will need to create a new instance of Mount for each component instance:
class Mount {
...
}
// export the Mount class
export default Mount;
class example extends component{
constructor(props) {
super(props);
// create an instance of Mount for each component instance
this.mount = new Mount();
}
componentDidMount=async()=>{
this.mount.setMounted(true);
await this.loadScreen();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
async() => {
await this.loadScreen();
}
);
}
loadScreen=async()=>{
//some other stuff
if(this.mount.getMounted()){//second time value is false
this.setState({value:'value'});
}
}
componentWillUnmount() {
this.mount.setMounted(false);
}
//renderview where i call example2 on buttonclick
}
Notice the addition of the constructor and the use of this.mount instead of mount throughout.

Vuex changes not impacting modules

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

How to observe/subscribe to changes in the state in Aurelia Store?

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

Reflux Action Not Reaching Method

I have build a store with actions like below:
import Reflux from 'reflux'
export const AuthActions = Reflux.createActions(['updateAuth', 'otherThing'])
export class AuthStore extends Reflux.Store
{
constructor()
{
super()
this.state = {
authToken: null,
authUser: null
}
this.listenables = AuthActions
}
otherThing()
{
debugger
console.log("OTHER THINNGGS")
}
updateAuth(token, user)
{
debugger
console.log("DODOODO")
this.setState({authToken: token, authUser: user})
}
}
However, anytime I import AuthActions and call AuthActions.otherThing() or AuthActions.updateAuth(token, user) I never reach those debuggers and nothing is printed to console, as if the methods are never called. I have tried renaming to onUpdateAuth and onOtherThing as well with no change.
Turns out, my store was never initialized. When you have a store, you have to either import and set the store in your component or initialize elsewhere in order to ensure the listenables are linked up to an instance of the store. In order to ensure my stores are always available, I create a single instance of the stores and exported that instead of the class itself.

Realm & React Native - Best practice to implement auto-updates?

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)});
}
});