7

I have the following React Native code that runs the press() method when a user taps an image. I want to get the itemIndex prop from the event object. I set a break point in the press method and added some expressions to the Watch. From the Watch I determined that the target (event origination) from the event is the Image which is correct. The itemIndex prop is also available. The element being processed is the currentTarget, the Watch sees it's a "RCTView" and I was expecting a TouchableOpacity, so maybe underneath TouchableOpacity is a View? The currentTarget itemIndex prop is undefined, why? How can I get the props from the currentTarget?

I want to do it this way to avoid creating addition methods for each rendered item.

FYI, ref={(c) => this._input = c} will not work because it's being run in a loop. onPress={(e) => this.press(e, i)} creates a new function which I'm trying to avoid.

Watch

  1. target._currentElement.props.itemIndex: 2
  2. target._currentElement.type.displayName: "RCTImageView"
  3. currentTarget._currentElement.props.itemIndex: undefined
  4. currentTarget._currentElement.type.displayName: "RCTView"

    press: function(event){
        var currentTarget = ReactNativeComponentTree.getInstanceFromNode(event.currentTarget);
        var target = ReactNativeComponentTree.getInstanceFromNode(event.target);
        var currentTargetIndex = currentTarget._currentElement.props.itemIndex;
        var targetIndex = target._currentElement.props.itemIndex;
        var url = this.state.data.items[currentTargetIndex].url;
        Linking.openURL(url).catch(err => console.error('An error occurred', err));
    },
    
    render: function() {
    return (
        <ScrollView horizontal={true} showsHorizontalScrollIndicator={false} style={styles.galleryView}>
            {
                this.state.data.items.map((data, i) =>
                    <TouchableOpacity itemIndex={i} key={i} activeOpacity={0.5} onPress={this.press} >
                        <Image itemIndex={i} key={i} source={{uri:data.previewImageUri}} style={styles.galleryImage} />
                    </TouchableOpacity>
                )
            }
        </ScrollView>
    );
    }
    
Andy Root
  • 73
  • 1
  • 4

3 Answers3

2

I actually came across this same issue recently, I found two different ways you could approach this. The easier way of doing it is altering your onPress to pass an index to your press function, this is the 2nd way of doing it:

press: function(event, index){
  var url = this.state.data.items[index].url;
  Linking.openURL(url).catch(err => console.error('An error occurred', err));
},

render: function() {
  return (
    <ScrollView
      horizontal={true}
      showsHorizontalScrollIndicator={false}
      style={styles.galleryView}
    >
    {
      this.state.data.items.map((data, i) =>
        <Images data={data} key={i} index={i} press={this.press} />
      )
    }
    </ScrollView>
    );
}

const Images = (props) => {
  const imageClicked = (e) => {
    props.press(e, props.index);
  }
  return (
    <TouchableOpacity activeOpacity={0.5} onPress={imageClicked} >
      <Image source={{uri:props.data.previewImageUri}} style={styles.galleryImage} />
    </TouchableOpacity>
  )
}
Florent Roques
  • 2,424
  • 2
  • 20
  • 23
Matt Aft
  • 8,742
  • 3
  • 24
  • 37
  • 1
    AFAIK, onPress={(e) => this.press(e, i)} creates a new function which I'm trying to avoid. I want to only pass a reference to my press handler and retrieve the index from the event object. – Andy Root Feb 08 '17 at 22:50
  • 1
    Yea, ok I updated my answer to the 2nd way of doing it. Basically you create component and pass down both the index and the press function as properties and create a new function that will call your initial press function but now passing through both the event and the index...let me know if you want me to clarify further. – Matt Aft Feb 08 '17 at 23:14
  • 1
    Thanks Matt! I got it. This solution looks more like a work around to a React Native bug. I was hoping for something more elegant or that I was just doing something wrong. I will let this topic hang for a few days and see if there is a more elegant or "React Native way" to perform this. If not I will redirect to https://github.com/facebook/react-native/issues – Andy Root Feb 08 '17 at 23:56
  • 1
    sounds good, also I just re-read your question and I understand now what you're looking for. This isn't a bug, you can't just pass down properties to RN's components other than the ones they have defined. In order to use properties that are not given in their docs you have to do what I did in my example and create your own component that wraps their components and pass your own properties and do your own logic through that process. This is the RN way to do it, you just need to move 'Images' component into its own file and import it in and the code will look very clean. – Matt Aft Feb 09 '17 at 00:23
2

You could make your event handler a curried function that accepts extra parameters.

 //Curried function for onPress event handler
  handleOnPress = (someId, someProp) => event => {
    //USE someProp ABOVE TO ACCESS PASSED PROP, WHICH WOULD BE undefined IN THIS CASE
    //Use event parameter above to access event object if you need
    console.log(someProp)

    this.setState({
      touchedId: someId
    })
  }

Checkout the working snack below

https://snack.expo.io/@prashand/accessing-props-from-react-native-touch-event

PrashanD
  • 2,643
  • 4
  • 28
  • 58
0

Binding the needed information to a callback and assigning one to each child avoids recreating the callback on every render of children.

class Hello extends React.Component{
  state = { names: this.props.names.map((name, i) => {
      return Object.assign({
        onClick: this._onClick.bind(this, i, this.props),
      }, name)
    }),
  };

  _onClick(ind, _props, e) {
    alert('props:' + JSON.stringify(_props));
  }

  render() {
    const { names } = this.state;
    return (
      <div>
        { names.map((name, i) => (
            <div key={i}>Name: <input value={ name.first } onClick={ name.onClick } /></div>
          ))}
      </div>
  )}
}

ReactDOM.render(
  <Hello names={[{first:'aaa'},{first:'bbb'},{first:'ccc'}]}/>,
  document.getElementById('container')
);

JS Fiddle