Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Native UI components: RCTBubblingEventBlock/RCTDirectEventBlock do not seem to work

I have a custom native view within an Ignite project. I am trying to set up a communication from Objective-C to React Native. The communication from React Native to iOS works with the HTML injection, but not the other way around. I have tried using both RCTBubblingEventBlock and RCTDirectEventBlock, but neither work. Here is the entirety of my implementation. I have changed the names of the components of course, and just left the essential implementation for your understanding of what has been done so far:

Objective-C code:

// CustomViewManager.h

#import "RCTViewManager.h"

@interface CustomViewManager : RCTViewManager

@end


// CustomViewManager.m

#import "CustomViewManager.h"
#import "CustomView.h"

#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"

@implementation CustomViewManager

RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(htmlInjection, NSString)
RCT_EXPORT_VIEW_PROPERTY(onEventA, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onEventB, RCTDirectEventBlock)

- (UIView *) view {
  return [CustomView new];
}

@end

// CustomView.h

#import "RCTView.h"

@interface CustomView : RCTView

@property (nonatomic, assign) NSString *htmlInjection;
@property (nonatomic, copy) RCTDirectEventBlock onEventA;
@property (nonatomic, copy) RCTDirectEventBlock onEventB;

@end

// CustomView.m

#import "CustomView.h"
#import "RCTUtils.h"

#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"
#import "MyExternalComponent.h"

@interface CustomView () <UIWebViewDelegate>
@property (nonatomic, strong) UIWebView* webView;

@end


- (void) setUpWebView {
  if (!_webView) {
    [self setWebView: [UIWebView new]];
    _webView.delegate = self;
    [self addSubview:_webView];
  }
}

- (instancetype)init
{
  self = [super init];
  [self setUpWebView];
  return self;
}

- (id)initWithFrame:(CGRect)frame
{
  self = [super initWithFrame:frame];
  if (self) {
    [self setUpWebView];
  }
  return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  if ((self = [super initWithCoder:aDecoder])) {
    [self setUpWebView];
  }
  return self;
}

- (void) layoutSubviews {
  [super layoutSubviews];
  CGRect frame = self.frame;
  self.webView.frame = frame;
}

#pragma mark - External methods.

- (void) setHtmlInjection:(NSString *)html {
  [_webView loadHTMLString:html baseURL:nil];
}

#pragma mark - Non-React component methods.

- (void) fetchData {
    [MyExternalComponent getData:^(NSString *dataA, NSError *error){
            if(error) {
                NSLog(@"Here be errors: %@", error);
                _onEventB(@{@"myError": error.localizedDescription});
            } else {
                _onEventA(@{@"myData": dataA});
            }
        }]
}

@end

React Native JavaScript code:

// MyCustomView.js

import React from 'react';
import { requireNativeComponent } from 'react-native';

class MyCustomView extends React.Component {
  constructor(props) {
    super(props);
    this._onEventA= this._onEventA.bind(this);
    this._onEventB= this._onEventB.bind(this);
  }
  _onEventA(event: Event) {
    if (!this.props.onEventA) {
      return;
    }
    this.props.onEventA(event.nativeEvent.myData);
  }
  _onEventB(event: Event) {
    if (!this.props.onEventA) {
      return;
    }
    this.props._onEventB(event.nativeEvent.myError);
  }
  render() {
    return (
      <CustomView
        {...this.props}
        onEventA={this._onEventA}
        onEventB={this._onEventB}
      />
    );
  }
}

MyCustomView.propTypes = {
  htmlInjection: React.PropTypes.string,
  onEventA: React.PropTypes.func,
  onEventB: React.PropTypes.func,
};

var CustomView = requireNativeComponent('CustomView', MyCustomView);

module.exports = MyCustomView;

// CustomWrapperContainer.js

class CustomWrapperContainer extends React.Component {

  api: Object;
  constructor (props: Object) {
    super(props);
    this.state = {
      htmlInjection: '',
      myDataA: 'Some placeholder text'
    };

    this.api = RestApi.create();
  }

  render () {
    return (
      <View style={styles.container}>
        <KeyboardAvoidingView behavior='position'>
          <Text>{this.state.myDataA}</Text>
          <MyCustomView
            style={styles.myStyle}
            htmlInjection={this.state.htmlInjection}
            onEventA={this.handleEventA.bind(this)}
            onEventB={this.handleEventB.bind(this)}
          />
        </KeyboardAvoidingView>
      </View>
    )
  }

  handleEventA = (data) => {
    console.log('on Event A', data);
    this.setState({myDataA: data})
  };

  handleEventB = (error) => {
    console.log('On Event B', error);
  };
}

const mapStateToProps = (state) => {
  return {
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(CustomWrapperContainer)

I have followed the example from React Native itself and also several others, but I have had no luck so far in getting an event to pass from iOS to React Native. Neither have I been able to find significant help in this matter from existing articles.

Ignite uses react version 15.3.2. Perhaps that's the issue? Or the version of some other dependency there? I'm not sure. I'd greatly appreciate any help or leads.

P.S.: I've been running this on both devices and simulators running iOS 9.2 through 10.0 and I don't see any change in behaviour, so that's not the issue.

like image 803
Rameez Hussain Avatar asked Dec 02 '16 11:12

Rameez Hussain


1 Answers

I ran into a different issue with RCTBubblingEventBlock. All RCTBubblingEventBlock must be prefixed with on. This is not currently noted under the documentation.

For example:

onMyEvent //will work
myEvent   //no good
like image 129
Ulises Giacoman Avatar answered Sep 20 '22 22:09

Ulises Giacoman