13

UPDATED

DESCRIPTION

I have a listener on a Realm Object for getting updates. When there is an update on the server (or in the client) the function provided to the listener calls setState({}).

The strange part is that even if the console says that everything is ok, and it shows that the render method was called with correct data, I can't see any updates to my app.

If I tap on the screen randomly (after 1s,2s, 20s...) the UI magically updates and everything is correct.

If I do the same setState with a function called from a button it works, I guess because the animation of the button triggers the UI update.

Thanks for reading this.

STEP TO REPRODUCE

You have to update the server_url and credential in order to work.

react-native init test npm install realm react-native link realm

Since realm is not ready for 64-bit you must also be sure t compile only in 32bit in order to avoid app crashing when launched

use this code:

App.js

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';

import Realm from 'realm'

import { SERVER_URL } from "./config/realm";
import { Utente } from "./config/schema";


export default class App extends Component {

  loginAsync = async () => {

    var realm_user = Realm.Sync.User.current

    if(!realm_user){
      const credentials = Realm.Sync.Credentials.usernamePassword('admin', '******' ,false);
      realm_user = await Realm.Sync.User.login(SERVER_URL, credentials);
    }

    const config = realm_user.createConfiguration({
      schema: [
        Utente, 
        Realm.Permissions.Permission,
        Realm.Permissions.User,
        Realm.Permissions.Role],
      schemaVersion: 1,
    });

    this.realm = new Realm(config);


    var connectedUserData = this.realm.objects("Utente").filtered("id = $0", realm_user.identity)
    connectedUserData.subscribe()


    connectedUserData.addListener((connectedUserData)=>{

      if(connectedUserData[0]){
        this.setState({
          connectedUserData: connectedUserData[0]
        })
      }

    })

  }

  constructor(props){
    super(props)

    this.loginAsync()

    this.state = {
      connectedUserData: {
        nome: 'not loaded'
      }
    }

  }



  render() {
    return (
      <View style={styles.container}>
        <Text>{ this.state.connectedUserData.nome }</Text>
      </View>
    );
  }
}



Schema.js

export const Utente = {
    name: "Utente",
    primaryKey: "id",
    properties: {
        id: "string",
        nome: 'string?',
        permissions: '__Permission[]'
    }
}

Package.json

{
  "name": "testBaseRealm",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest"
  },
  "dependencies": {
    "react": "16.6.3",
    "react-native": "0.57.7",
    "realm": "^2.27.0-rc.3"
  },
  "devDependencies": {
    "@babel/core": "7.4.4",
    "@babel/runtime": "7.4.4",
    "babel-jest": "24.8.0",
    "jest": "24.8.0",
    "metro-react-native-babel-preset": "0.54.1",
    "react-test-renderer": "16.6.3"
  },
  "jest": {
    "preset": "react-native"
  }
}

Some other strange things :

  • If I remote debug js to react native debugger (on Windows, but I guess is the same) the problem disappears.
  • The same thing happens on 3 different devices( 2 real, 1 emulator)
DxW
  • 1,414
  • 3
  • 11
  • 23

3 Answers3

8

In my case, I just stop the debugger (CMD+D), and that weird behavior went away. enter image description here

Blarz
  • 294
  • 3
  • 6
2

UPDATE 2

setState({}) doesn't work when is inside the listener callback. I've just done a test changing the code in componentDidMount of Home.js, and in this way, it works.

It doesnt work because you are not binding the method that is calling it. it is beyond component context so setState is not there.

Do

   openRealmAndLogin = (realm_user) => {...}

instead of regular function as this one will bind the function to context. e.g. you can also bind it in constructor (but from what i've seen you are already doing something similar for other function - so better to keep it consistent)

Shiroo
  • 666
  • 4
  • 11
  • I tryed but doing this not solve the problem :( setState is called in the listener callback, and I can see the data changing and the render method firing in the console log. The only problem is that UI thread (maybe?) doesn't render the changes until I tap – DxW May 12 '19 at 14:30
1

I would suggest that you change the key of the element; this will force it to reload whatever happens.

Ex:

{
  articoli.map(articolo => {
    const isLoved = connectedUserData.loved_articles.filtered("id = $0", articolo.id ).length
    const isLiked = connectedUserData.liked_articles.filtered("id = $0", articolo.id ).length
    const numCommenti = articolo.commenti.length

    return (
      <SchedaArticolo
        key={ `ALL_${articolo.id}_${isLoved}_${isLiked}_${numCommenti}` }
        articolo={articolo}
        isLoved={isLoved}
        isLiked={isLiked}
        numCommenti={numCommenti}
      />
    )
  })
}
Roc Khalil
  • 1,365
  • 6
  • 22
  • @DxW can you add a callback for the setState and make sure it’s fired when the article is added ? this.setState({...}, ()=> alert(1)) for example; also you have to remove the forceUpdate since the state is changing :-) – Roc Khalil May 11 '19 at 15:21
  • I've tried, it's the same. I also tried using key = {Date().getTime()} to be sure to force re-rendering at each cycle but nothing. The strange thing is that if you tap on the screen everything is updated well. But in console, nothing happens and the last rendering cycle (with correct data) happened when the notification was received from the listener. – DxW May 11 '19 at 15:24
  • yes, the callback fires. Since there is an interaction after I close the alert the ui get updated. But I guess only because of the interaction. I'm uploading an example. If I use a button to change the state it works well, "surprisingly" even with a TouchableWithoutFeedback (for avoiding the animations that may trigger a re-render). – DxW May 11 '19 at 15:45
  • @DxW very weird.. I have no clue to be honest :') – Roc Khalil May 13 '19 at 07:09
  • It's very strange because in other apps it works, and even more strange it works also in this app in other scenes... – DxW May 13 '19 at 13:16
  • @DxW sorry for not following back; did you end up solving it ? – Roc Khalil May 20 '19 at 08:10
  • @DxW stop using realm, maybe they'll fix it when people start moving away from their solution :') hahaha; to be honest, I'm not familiar with it, so I really don't have any other solution than this one.. – Roc Khalil Jun 24 '19 at 08:52
  • I hope I found a solution, but for me the most important thing is to understand why it happens. Read this if you are interested https://github.com/facebook/react-native/issues/5712#issuecomment-195660010 It says that the problem is that js code is executed outside the RN event loop, but they talk abut Chrome debugger. I think that is related to the fact that both Chrome debugger and RealmDB are connected to native code... – DxW Jun 24 '19 at 12:31
  • @DxW this is very weird; I didn't know that React acts this way to be honest. It's a little bit weird to see the dom only renders when an action is pressed; they should work on a fix for this :-/ – Roc Khalil Jun 24 '19 at 14:58
  • 1
    I confirm that the workaround works. I think is related to this "setImmediate is executed at the end of the current JavaScript execution block, right before sending the batched response back to native. Note that if you call setImmediate within a setImmediate callback, it will be executed right away, it won't yield back to native in between. The Promise implementation uses setImmediate as its asynchronicity primitive." from https://facebook.github.io/react-native/docs/timers – DxW Jun 24 '19 at 15:32