Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue with @computed after update to 4.0.0 on React Native #1396

Closed
psycura opened this issue Mar 13, 2018 · 20 comments
Closed

Issue with @computed after update to 4.0.0 on React Native #1396

psycura opened this issue Mar 13, 2018 · 20 comments

Comments

@psycura
Copy link

psycura commented Mar 13, 2018

Hello, and thank you for this awesome library.
I`m working on RN project, and after update to the 4.0.0 version, stuck with some strange issue. (when downgrading to 3.6.2 everything work fine).
So i have a rootStore:

`import GuiStore from './GuiStore';
import UserStore from './UserStore';
import SchoolsStore from './SchoolsStore';

class RootStore {
constructor () {
this.guiStore = new GuiStore ( this );
this.schoolsStore = new SchoolsStore ( this );
this.userStore = new UserStore ( this );
}
}

const appStore = new RootStore ();
export { appStore };`

and relevant inner Stores:

SCHOOL STORE
`import { action, computed, observable, toJS } from 'mobx';
import { find, map } from 'lodash'
import firebase from 'react-native-firebase';

const db = firebase.firestore ();

class SchoolsStore {

@observable _schools        = null;
@observable fetchInProgress = true;
@observable _school         = null;

constructor ( root ) {
    this.root = root;
    this.toggleFetchProgress ( true );
    this.fetchSchools ().then ( () => {
        this.toggleFetchProgress ( false );
    } )
}

@computed get schools () {
    return toJS ( this._schools )
}

@computed get schoolsList () {
    return map ( this.schools, s => {
        return {
            label: s.name,
            value: s.id
        }
    } )
}

@computed get school () {
    if ( this._school ) {
        return find ( this.schools, school => school.id === this._school )
    }
}

@action
toggleFetchProgress ( status ) {
    this.fetchInProgress = status;
}

@action
setCurrentSchool ( id ) {
    this._school = id;
}

fetchSchools () {
    const schoolsRef = db.collection ( 'schools' ).where ( 'subscription.active', '==', true );
    return schoolsRef.get ().then ( collection => {
        const schools = [];
        collection.forEach ( doc => {
            schools.push ( doc.data () )
        } );
        this._schools = [...schools]
    } )
}

}

export default SchoolsStore;
`

USER STORE
`import { action, computed, observable, toJS } from 'mobx';
import firebase from 'react-native-firebase';

const db = firebase.firestore ();
const FCM = firebase.messaging ();

class UserStore {

@observable userRef         = null;
@observable _user           = null;
@observable token           = null;
@observable fetchInProgress = true;

constructor ( root ) {
    this.root = root;
    
    this.toggleFetchProgress ( true );
    this.msgInit ().then ( () => {
        if ( this.user.activeSchool ) {
            this.root.schoolsStore.setCurrentSchool ( this.user.activeSchool );
        }
        this.toggleFetchProgress ( false );
    } )
}

@computed get user () {
    return toJS ( this._user ) || null;
}

@action
toggleFetchProgress ( status ) {
    this.fetchInProgress = status;
}

@action
updateUserField ( { field, value } ) {
    const currentUser = toJS ( this._user );
    if ( currentUser[field] !== value ) {
        currentUser[field] = value;
        switch ( field ) {
            case 'activeSchool':
                if ( currentUser.schoolsList.indexOf ( value ) < 0 ) {
                    currentUser.schoolsList.push ( value );
                }
                break;
            default:
                break;
        }
        this._user = { ...currentUser };
        
        this.dbUpdateUser ( currentUser );
    }
}


dbUpdateUser ( user ) {
    this.userRef.set ( user );
}

@action
async fetchUser ( token ) {
    const ref = db.collection ( 'appUsers' ).where ( 'token', '==', token );
    return ref.get ().then ( async ( collection ) => {
        if ( collection._docs.length ) {
            collection.forEach ( async ( doc ) => {
                if ( doc.exists ) {
                    this.userRef = db.collection ( 'appUsers' ).doc ( doc.id );
                    this._user   = doc.data ();
                    doc.data ();
                } else {
                    await this.createUser ( token )
                }
            } )
        } else {
            await this.createUser ( token )
        }
        
    } )
}

async createUser ( token ) {
    const ref    = db.collection ( 'appUsers' ).doc ();
    this.userRef = db.collection ( 'appUsers' ).doc ( ref.id );
    const user   = {
        token,
        activeSchool: '',
        role:         'student',
        schoolsList:  []
    };
    await  ref.set ( user );
    await this.fetchUser ();
}

   
msgInit () {
    return new Promise ( async ( resolve ) => {
        FCM.requestPermissions ();
        await FCM.getToken ().then ( async ( token ) => {
            this.token = token;
            await this.fetchUser ( token );
        } );
        
        FCM.onMessage ( payload => {
            console.log ( 'message received' );
        } );
        resolve ();
    } );
    
}

}

export default UserStore;
`

The issue is happens here - at Login Screen:

import React, { Component } from 'react';
import { ScrollView, Image, Text, Picker, StyleSheet, ImageBackground } from 'react-native';
import { observer, inject } from 'mobx-react'
import { Container } from '../components/generic';
import Images from '@assets/images'
import Head from '../components/Login/Head'
import Footer from '../components/Login/Footer'
import FormWrapper from '../components/Login/FormWrapper'
import { computed, when } from 'mobx'

class LoginScreen extends Component {
    
    constructor ( props ) {
        super ( props );
        this.userStore    = props.appStore.userStore;
        this.schoolsStore = props.appStore.schoolsStore;
    }
    
    @computed get fetchInProgress () {
        const userFetching    = this.userStore.fetchInProgress;
        const schoolsFetching = this.schoolsStore.fetchInProgress;
        return userFetching && schoolsFetching;
    }
    
    componentDidMount () {
        
        const { navigation } = this.props;
        
        when (
            () => !this.fetchInProgress && this.userStore.user,
            () => {
                const user = this.userStore.user;
                if ( user && user.activeSchool ) {
                    navigation.navigate ( 'drawerStack' )
                }
            }
        )
    }
    
    navigateTo = ( route ) => {
        this.props.navigation.navigate ( route )
    };
    
    render () {
        
        const { bgImage } = styles;
        
        return (
            <Container column height='100%' width='100%' justify='space-between' alignItems='center'>
                <ImageBackground style={bgImage} source={Images.loginBg}>
                    <Container height='93%' alignItems='center' column justify='flex-start'>
                        <Head mode='school'/>
                        {
                            !this.fetchInProgress && this.userStore.user && !this.userStore.user.activeSchool
                            &&
                            <FormWrapper navigationHandler={this.navigateTo}/>
                        }
                    </Container>
                    <Footer/>
                </ImageBackground>
            </Container>
        );
    }
}

const styles = StyleSheet.create ( {
    bgImage: {
        height: '100%',
        width:  '100%'
    }
} );

export default inject ( 'appStore' ) ( observer ( LoginScreen ) );

on launch i receive an error

maximum call stack size exceeded

I found out, that if i comment the @computed get fetchInProgress and a when block, and
{ !this.fetchInProgress && this.userStore.user && !this.userStore.user.activeSchool && <FormWrapper navigationHandler={this.navigateTo}/> }

no error happen, so i think that issue come from using @computed properties.
Why is this happen?

@mweststrate
Copy link
Member

That is a lot of code. Could you minimize it?

@psycura
Copy link
Author

psycura commented Mar 14, 2018

Actually, yes.
I tried very simple component, and this error still happen:


import React, { Component } from 'react';
import { ScrollView, Image, Text, Picker, StyleSheet, ImageBackground } from 'react-native';
import { observer, inject } from 'mobx-react'

import { computed, when } from 'mobx'

class LoginScreen extends Component {
    
    @computed get test () {
        return 'test'
    }
    
    render () {
        
        return (
            <Text>
                {this.test}
            </Text>
        );
    }
}

export default observer ( LoginScreen );


`
RangeError: Maximum call stack size exceeded

This error is located at:
in LoginScreen (at SceneView.js:17)
in SceneView (at CardStack.js:466)
in RCTView (at View.js:78)
in View (at createAnimatedComponent.js:147)
in AnimatedComponent (at Card.js:12)
in Card (at PointerEventsContainer.js:39)
in Container (at CardStack.js:498)
in RCTView (at View.js:78)
in View (at CardStack.js:414)
in RCTView (at View.js:78)
in View (at CardStack.js:413)
in CardStack (at CardStackTransitioner.js:67)
in RCTView (at View.js:78)
in View (at Transitioner.js:142)
in Transitioner (at CardStackTransitioner.js:19)
in CardStackTransitioner (at StackNavigator.js:41)
in Unknown (at createNavigator.js:13)
in Navigator (at createNavigationContainer.js:226)
in NavigationContainer (at SceneView.js:17)
in SceneView (at SwitchView.js:18)
in SwitchContainer (at withCachedChildNavigation.js:69)
in withCachedChildNavigation(SwitchContainer) (at SwitchNavigator.js:11)
in Unknown (at createNavigator.js:13)
in Navigator (at createNavigationContainer.js:226)
in NavigationContainer (at App.js:27)
in RCTView (at View.js:78)
in View (at KeyboardAvoidingView.js:205)
in KeyboardAvoidingView (at App.js:26)
in Provider (at App.js:25)
in App (at renderApplication.js:35)
in RCTView (at View.js:78)
in View (at AppContainer.js:102)
in RCTView (at View.js:78)
in View (at AppContainer.js:122)
in AppContainer (at renderApplication.js:34)

  handleException @ D:\Sites\work\eton4y…ptionsManager.js:65
  showErrorDialog @ D:\Sites\work\eton4y…nderer-dev.js:11170
  logCapturedError @ D:\Sites\work\eton4y…nderer-dev.js:11180
  captureError @ D:\Sites\work\eton4y…nderer-dev.js:12116
  renderRoot @ D:\Sites\work\eton4y…nderer-dev.js:11950
  performWorkOnRoot @ D:\Sites\work\eton4y…nderer-dev.js:12742
  performWork @ D:\Sites\work\eton4y…nderer-dev.js:12660
  performSyncWork @ D:\Sites\work\eton4y…nderer-dev.js:12622
  requestWork @ D:\Sites\work\eton4y…nderer-dev.js:12535
  scheduleWorkImpl @ D:\Sites\work\eton4y…nderer-dev.js:12374
  scheduleWork @ D:\Sites\work\eton4y…nderer-dev.js:12321
  scheduleRootUpdate @ D:\Sites\work\eton4y…nderer-dev.js:13025
  updateContainerAtExpirationTime @ D:\Sites\work\eton4y…nderer-dev.js:13059
  updateContainer @ D:\Sites\work\eton4y…nderer-dev.js:13077
  render @ D:\Sites\work\eton4y…nderer-dev.js:13796
  renderApplication @ D:\Sites\work\eton4y…erApplication.js:58
  run @ D:\Sites\work\eton4y…\AppRegistry.js:103
  runApplication @ D:\Sites\work\eton4y…\AppRegistry.js:196
  __callFunction @ D:\Sites\work\eton4y…MessageQueue.js:353
  (anonymous) @ D:\Sites\work\eton4y…MessageQueue.js:118
  __guardSafe @ D:\Sites\work\eton4y…MessageQueue.js:316
  callFunctionReturnFlushedQueue @ D:\Sites\work\eton4y…MessageQueue.js:117
  (anonymous) @ debuggerWorker.js:72

`

@mweststrate
Copy link
Member

mweststrate commented Mar 14, 2018 via email

@psycura
Copy link
Author

psycura commented Mar 14, 2018

Yes, here
https://github.com/psycura/ReactNativeAndMobx4test

i created a simplest ReactNative App ever, take a look at the App component.
Only one @computed used.

@psycura
Copy link
Author

psycura commented Mar 14, 2018

And this issue is happen only in React Native.

In regular React app everything work just fine

@seantimm
Copy link

We're seeing the same problem in our React Native project.

@mweststrate
Copy link
Member

mweststrate commented Mar 14, 2018 via email

@seantimm
Copy link

Nope... same error.

@AntonPuko
Copy link

Same here. Fails only with @computed decorator version. decorate(observer(Component), { test: computed}) - works as expected.

@mweststrate
Copy link
Member

Could it relate to hot reloading? (Dunno if that can be disabled?). Does @computed({}) get .... work better?

@AntonPuko
Copy link

I have hot-reloading disabled. Same fail with @computed({})

@mweststrate mweststrate changed the title Issue with @computed after update to 4.0.0 Issue with @computed after update to 4.0.0 on React Native Mar 15, 2018
mweststrate added a commit that referenced this issue Mar 15, 2018
@mweststrate
Copy link
Member

Just released a beta patch, can one verify whether this solves the issue? [email protected]

@psycura
Copy link
Author

psycura commented Mar 15, 2018

Yes, it works!!
Thank you

@mweststrate
Copy link
Member

Released as 4.1!

@rzcoder
Copy link

rzcoder commented Mar 20, 2018

This change breaks compatible with mobx 4.0.0 & 3.x

import { computed, observable } from "mobx";

class A {
  @observable _id;
  constructor() {
    this._id = "A";
  }
  @computed get id() {
    return "A_id: " + this._id;
  }
}

class B extends A {
  constructor() {
    super();
    this._id = "B";
  }
  get id() {
    return "B_id: " +this._id;
  }
}

let a = new A();
let b = new B();

console.log(a.id); // A_id: A
console.log(b.id); // A_id: B

in 3.6.2 & 4.0.0

A_id: A
B_id: B

in 4.1.0

A_id: A
A_id: B

@mweststrate
Copy link
Member

mweststrate commented Mar 20, 2018 via email

@rzcoder
Copy link

rzcoder commented Mar 21, 2018

ok, #1443

@Tresky
Copy link

Tresky commented Aug 1, 2018

I am experiencing this same issue again in MobX 5.0.3.

@xanderdeseyn
Copy link

I am experiencing this issue as well.

@mweststrate
Copy link
Member

Please open new issues for new issues

@mobxjs mobxjs locked as resolved and limited conversation to collaborators Aug 24, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants