facebookarchive / react-360

Create amazing 360 and VR content using React
https://facebook.github.io/react-360
Other
8.73k stars 1.23k forks source link

Undefined return values from native modules in React components #193

Closed ryanmeasel closed 7 years ago

ryanmeasel commented 7 years ago

Description

Bug - The return value of a native module function is undefined when accessed in a React component.

Expected behavior

test bool: true
test int: 7
test str: 'test string'

Actual behavior

test bool: undefined
test int: undefined
test str: undefined

Reproduction

client.js:

import {VRInstance} from 'react-vr-web'
import {Module} from 'react-vr-web'

function init (bundle, parent, options) {
  const testModule = new TestModule()

  const vr = new VRInstance(bundle, 'nativeModuleBug', parent, {
    nativeModules: [testModule],
    ...options
  })
  vr.render = function () {
    // Any custom behavior you want to perform on each frame goes here
  }
  // Begin the animation loop
  vr.start()
  return vr
}

export default class TestModule extends Module {
  constructor () {
    super('TestModule')
  }

  getStr () {
    return 'test string'
  }

  getInt () {
    return 7
  }

  getBool () {
    return true
  }
}

window.ReactVR = {init}

index.vr.js:

import React from 'react'
import {
  AppRegistry,
  asset,
  NativeModules,
  Pano,
  Text,
  View
} from 'react-vr'

const TestModule = NativeModules.TestModule

export default class nativeModuleBug extends React.Component {

  constructor (props) {
    super(props)

    console.log('test bool: ' + TestModule.getBool())
    console.log('test int: ' + TestModule.getInt())
    console.log('test str: ' + TestModule.getStr())
  }

  render () {
    return (
      <View>
      </View>
    )
  }
};

AppRegistry.registerComponent('nativeModuleBug', () => nativeModuleBug)

Additional Information

andrewimm commented 7 years ago

This is actually expected behavior. Native Modules are called across an asynchronous bridge, so there is no way to synchronously return a value to the original caller; if you did so, your react application would be blocked until a response came back.

Instead, if you need to return a result, you can do one of two things: 1) Have your native module method return a promise. 2) Have your native module method accept a callback that is triggered with some value

When a method is called with a callback, a unique identifier is passed to the native side. To trigger it, you can instruct the React Native Context to call that callback with a specific set of arguments.

I highly recommend that you look at how the Native Modules supporting React VR are implemented for more examples: https://github.com/facebook/react-vr/tree/master/ReactVR/js/Modules

This is an example of a Promise-based API (the $ prefix means it returns a Promise on the React side): https://github.com/facebook/react-vr/blob/master/ReactVR/js/Modules/LinkingManager.js#L49 Because it has a Promise API, the final two arguments of the method are assumed to be callbacks to either resolve or reject that promise.

ryanmeasel commented 7 years ago

Ah, that makes sense. Thanks for your help, Andrew!