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

How to get ref's style or width? #953

Closed
leecade opened this Issue Apr 21, 2015 · 23 comments

Comments

Projects
None yet
@leecade

leecade commented Apr 21, 2015

<View ref='view' style={{width: 100, marginTop: 10}}>
  <View ref='inner' style={{flex: 1}} />
</View>

I want to get the offsetWidth of refs.view, thanks

@gabro

This comment has been minimized.

Show comment
Hide comment
@gabro

gabro Apr 22, 2015

Contributor

I'm interested in this as well.
Especially when dealing with Images (that do not autosize) I would need to manually set a size according the computed "box size" of the container.

This is tightly related to #494

Contributor

gabro commented Apr 22, 2015

I'm interested in this as well.
Especially when dealing with Images (that do not autosize) I would need to manually set a size according the computed "box size" of the container.

This is tightly related to #494

@brentvatne

This comment has been minimized.

Show comment
Hide comment
@brentvatne

brentvatne Apr 22, 2015

Collaborator

@gabro @leecade - this is possible with the measure function: https://github.com/facebook/react-native/blob/master/Libraries/ReactIOS/NativeMethodsMixin.js#L61-L63

for example:

'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
} = React;


var TestIt = React.createClass({
  measureWelcome() {
    this.refs.welcome.measure(this.logWelcomeLayout);
  },

  logWelcomeLayout(ox, oy, width, height, px, py) {
    console.log("ox: " + ox);
    console.log("oy: " + oy);
    console.log("width: " + width);
    console.log("height: " + height);
    console.log("px: " + px);
    console.log("py: " + py);
  },

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome} ref="welcome">
          Welcome to React Native!
        </Text>
        <TouchableOpacity onPress={this.measureWelcome}>
          <Text>Measure it</Text>
        </TouchableOpacity>
      </View>
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('TestIt', () => TestIt);

The result of clicking the button:

2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "ox: 72"
2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "oy: 313"
2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "width: 231.5"
2015-04-22 15:34:44.970 [info][tid:com.facebook.React.JavaScript] "height: 24"
2015-04-22 15:34:44.971 [info][tid:com.facebook.React.JavaScript] "px: 72"
2015-04-22 15:34:44.971 [info][tid:com.facebook.React.JavaScript] "py: 313"

measure uses the RCTUIManager measure function. Notice the return values to the callback:

    callback(@[
      @(frame.origin.x),
      @(frame.origin.y),
      @(frame.size.width),
      @(frame.size.height),
      @(pagePoint.x),
      @(pagePoint.y)
    ]);

You may also be interested in measureLayout, which gives you:

callback(@[@(leftOffset), @(topOffset), @(width), @(height)]);
Collaborator

brentvatne commented Apr 22, 2015

@gabro @leecade - this is possible with the measure function: https://github.com/facebook/react-native/blob/master/Libraries/ReactIOS/NativeMethodsMixin.js#L61-L63

for example:

'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
} = React;


var TestIt = React.createClass({
  measureWelcome() {
    this.refs.welcome.measure(this.logWelcomeLayout);
  },

  logWelcomeLayout(ox, oy, width, height, px, py) {
    console.log("ox: " + ox);
    console.log("oy: " + oy);
    console.log("width: " + width);
    console.log("height: " + height);
    console.log("px: " + px);
    console.log("py: " + py);
  },

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome} ref="welcome">
          Welcome to React Native!
        </Text>
        <TouchableOpacity onPress={this.measureWelcome}>
          <Text>Measure it</Text>
        </TouchableOpacity>
      </View>
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

AppRegistry.registerComponent('TestIt', () => TestIt);

The result of clicking the button:

2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "ox: 72"
2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "oy: 313"
2015-04-22 15:34:44.969 [info][tid:com.facebook.React.JavaScript] "width: 231.5"
2015-04-22 15:34:44.970 [info][tid:com.facebook.React.JavaScript] "height: 24"
2015-04-22 15:34:44.971 [info][tid:com.facebook.React.JavaScript] "px: 72"
2015-04-22 15:34:44.971 [info][tid:com.facebook.React.JavaScript] "py: 313"

measure uses the RCTUIManager measure function. Notice the return values to the callback:

    callback(@[
      @(frame.origin.x),
      @(frame.origin.y),
      @(frame.size.width),
      @(frame.size.height),
      @(pagePoint.x),
      @(pagePoint.y)
    ]);

You may also be interested in measureLayout, which gives you:

callback(@[@(leftOffset), @(topOffset), @(width), @(height)]);
@brentvatne

This comment has been minimized.

Show comment
Hide comment
@brentvatne

brentvatne Apr 22, 2015

Collaborator

See also #977 where I get an element's opacity from over the bridge. Closing this as I believe the above comments resolve the issue, feel free to ping me if not and I will reopen. 💥

Collaborator

brentvatne commented Apr 22, 2015

See also #977 where I get an element's opacity from over the bridge. Closing this as I believe the above comments resolve the issue, feel free to ping me if not and I will reopen. 💥

@brentvatne brentvatne closed this Apr 22, 2015

@gabro

This comment has been minimized.

Show comment
Hide comment
@gabro

gabro Apr 23, 2015

Contributor

Thank you @brentvatne, that really helps!
Still, I haven't figured out which is the proper way of hooking into the layout events.
For instance, if I need to dynamically position a View after another has rendered, when should I perform the measure?

I tried in componentDidMount but it gives 0 for each measure...

Contributor

gabro commented Apr 23, 2015

Thank you @brentvatne, that really helps!
Still, I haven't figured out which is the proper way of hooking into the layout events.
For instance, if I need to dynamically position a View after another has rendered, when should I perform the measure?

I tried in componentDidMount but it gives 0 for each measure...

@brentvatne brentvatne reopened this Apr 23, 2015

@brentvatne

This comment has been minimized.

Show comment
Hide comment
@brentvatne

brentvatne Apr 23, 2015

Collaborator

cc @vjeux

Collaborator

brentvatne commented Apr 23, 2015

cc @vjeux

@gabro

This comment has been minimized.

Show comment
Hide comment
@gabro

gabro Apr 23, 2015

Contributor

Ok, I managed to achieve my goal by measuring the view asynchronously (with a setTimeout)

  componentDidMount() {
    setTimeout(this.measureHeader);
  },

  measureHeader() {
    this.refs.header.measure((ox, oy, width, height) => {
      this.setState({headerHeight: height});
    });
  },

then I use this.state.headerHeight in my render method to computer another element's position.

Still feels like a hack though...

Contributor

gabro commented Apr 23, 2015

Ok, I managed to achieve my goal by measuring the view asynchronously (with a setTimeout)

  componentDidMount() {
    setTimeout(this.measureHeader);
  },

  measureHeader() {
    this.refs.header.measure((ox, oy, width, height) => {
      this.setState({headerHeight: height});
    });
  },

then I use this.state.headerHeight in my render method to computer another element's position.

Still feels like a hack though...

@vjeux

This comment has been minimized.

Show comment
Hide comment
@vjeux

vjeux Apr 23, 2015

Contributor

What you've done is currently the best way to do it. It's far from being ideal but works well enough that we were able to solve all the problems we've thrown at it so far.

There's definitely a better abstraction that exists somewhere.

Contributor

vjeux commented Apr 23, 2015

What you've done is currently the best way to do it. It's far from being ideal but works well enough that we were able to solve all the problems we've thrown at it so far.

There's definitely a better abstraction that exists somewhere.

@gabro

This comment has been minimized.

Show comment
Hide comment
@gabro

gabro Apr 23, 2015

Contributor

Fair enough, thank you for the clarification @vjeux ;)

Contributor

gabro commented Apr 23, 2015

Fair enough, thank you for the clarification @vjeux ;)

@brentvatne

This comment has been minimized.

Show comment
Hide comment
@brentvatne

brentvatne Apr 23, 2015

Collaborator

Thanks for the update @gabro, glad it worked out for you

Collaborator

brentvatne commented Apr 23, 2015

Thanks for the update @gabro, glad it worked out for you

@brentvatne brentvatne closed this Apr 23, 2015

@sahrens

This comment has been minimized.

Show comment
Hide comment
@sahrens

sahrens Apr 24, 2015

Contributor

We've had a couple ideas here but no time to implement them.

One idea was to predeclare what you want to measure, then native would populate the value after computing layout so you can access it synchronously in React.

The other idea (which also has other benefits), is to have JS make an explicit call to run the layout rather than native choosing when to do it (currently "at the end"). This would let us sequence the measure calls after the layout calls so you don't have to do the setTimeout in componentDidMount to get the right data.

Contributor

sahrens commented Apr 24, 2015

We've had a couple ideas here but no time to implement them.

One idea was to predeclare what you want to measure, then native would populate the value after computing layout so you can access it synchronously in React.

The other idea (which also has other benefits), is to have JS make an explicit call to run the layout rather than native choosing when to do it (currently "at the end"). This would let us sequence the measure calls after the layout calls so you don't have to do the setTimeout in componentDidMount to get the right data.

@sahrens

This comment has been minimized.

Show comment
Hide comment
@sahrens

sahrens Apr 24, 2015

Contributor

If anyone wants to make either of these changes, let me know :)

Contributor

sahrens commented Apr 24, 2015

If anyone wants to make either of these changes, let me know :)

@brentvatne

This comment has been minimized.

Show comment
Hide comment
@brentvatne

brentvatne Apr 24, 2015

Collaborator

@sahrens - what kind of api did you have in mind for predeclaring what you want to measure?

Collaborator

brentvatne commented Apr 24, 2015

@sahrens - what kind of api did you have in mind for predeclaring what you want to measure?

@sahrens

This comment has been minimized.

Show comment
Hide comment
@sahrens

sahrens Apr 24, 2015

Contributor

Haven't really thought through the details yet, but on the fly: I think it would be ideal to bake it into the react core and build it in such a way that it would also work in web with DOM measure APIs, utilized via a prop on the component you want to measure (maybe pass it a function that is invoked with the new measure values, a la the new functional refs?

Maybe like

<View onLayoutChange={dims => this._dims = dims} />

Or

<View onLayoutChange={dims => this.setState({dims})} />

The advantage here is that you don't have to wait for a ref

Another option is as an explicit subscription:

<View ref={ref => ref.subscribeToLayout(dims => this.setState({dims}))}} />

Then implement subscribeToLayout in NativeMethodsMixin to register the reactTag with native, which would then either emit one layout event for all registered views which JS infra would then de-multiplex to the right callbacks per reactTag/nodeHandle.

It might make sense to build the first API (does anyone prefer the second?), but in a way more like the second behind the scenes rather than emitting separate events for every view, like we do for normal onChange events - not sure if that would be a pre-mature optimization, but seems like batching all the updates from one layout run into one event would be good.

On Apr 23, 2015, at 9:02 PM, Brent Vatne notifications@github.com wrote:

@sahrens - what kind of api did you have in mind for predeclaring what you want to measure?


Reply to this email directly or view it on GitHub.

Contributor

sahrens commented Apr 24, 2015

Haven't really thought through the details yet, but on the fly: I think it would be ideal to bake it into the react core and build it in such a way that it would also work in web with DOM measure APIs, utilized via a prop on the component you want to measure (maybe pass it a function that is invoked with the new measure values, a la the new functional refs?

Maybe like

<View onLayoutChange={dims => this._dims = dims} />

Or

<View onLayoutChange={dims => this.setState({dims})} />

The advantage here is that you don't have to wait for a ref

Another option is as an explicit subscription:

<View ref={ref => ref.subscribeToLayout(dims => this.setState({dims}))}} />

Then implement subscribeToLayout in NativeMethodsMixin to register the reactTag with native, which would then either emit one layout event for all registered views which JS infra would then de-multiplex to the right callbacks per reactTag/nodeHandle.

It might make sense to build the first API (does anyone prefer the second?), but in a way more like the second behind the scenes rather than emitting separate events for every view, like we do for normal onChange events - not sure if that would be a pre-mature optimization, but seems like batching all the updates from one layout run into one event would be good.

On Apr 23, 2015, at 9:02 PM, Brent Vatne notifications@github.com wrote:

@sahrens - what kind of api did you have in mind for predeclaring what you want to measure?


Reply to this email directly or view it on GitHub.

@fatuhoku

This comment has been minimized.

Show comment
Hide comment
@fatuhoku

fatuhoku Jul 7, 2015

@gabro I've tried to measure the root view with the code you posted above but no avail:

class MainComponent extends Component {
  componentDidMount() {
    // https://github.com/facebook/react-native/issues/953
    setTimeout(this.measureMainComponent);
  }
  measureMainComponent() {
    this.refs.rootView.measure((ox, oy, width, height) => {
      this.setState({rootViewHeight: height});
    });
  }
  render() {
    return (
      <View ref='rootView'/>
    );
  }
}

ios simulator screen shot 7 jul 2015 15 39 17

What am I doing wrong?

fatuhoku commented Jul 7, 2015

@gabro I've tried to measure the root view with the code you posted above but no avail:

class MainComponent extends Component {
  componentDidMount() {
    // https://github.com/facebook/react-native/issues/953
    setTimeout(this.measureMainComponent);
  }
  measureMainComponent() {
    this.refs.rootView.measure((ox, oy, width, height) => {
      this.setState({rootViewHeight: height});
    });
  }
  render() {
    return (
      <View ref='rootView'/>
    );
  }
}

ios simulator screen shot 7 jul 2015 15 39 17

What am I doing wrong?

@brentvatne

This comment has been minimized.

Show comment
Hide comment
@brentvatne

brentvatne Jul 7, 2015

Collaborator

@fatuhoku - you need to bind the fn you pass in to setTimeout to the component:

setTimeout(this.measureMainComponent.bind(this));

or

requestAnimationFrame(this.measureMainComponent.bind(this));

Collaborator

brentvatne commented Jul 7, 2015

@fatuhoku - you need to bind the fn you pass in to setTimeout to the component:

setTimeout(this.measureMainComponent.bind(this));

or

requestAnimationFrame(this.measureMainComponent.bind(this));

@fatuhoku

This comment has been minimized.

Show comment
Hide comment
@fatuhoku

fatuhoku Aug 11, 2015

@brentvatne Thanks binding seemed to work.

fatuhoku commented Aug 11, 2015

@brentvatne Thanks binding seemed to work.

@sahrens

This comment has been minimized.

Show comment
Hide comment
@sahrens

sahrens Sep 4, 2015

Contributor

Also note we now have onLayout implemented, so you can use that in some cases.

Contributor

sahrens commented Sep 4, 2015

Also note we now have onLayout implemented, so you can use that in some cases.

@oknixus

This comment has been minimized.

Show comment
Hide comment
@oknixus

oknixus commented Nov 27, 2015

mark

@uc-spr

This comment has been minimized.

Show comment
Hide comment
@uc-spr

uc-spr Aug 16, 2016

How can we get the x, y coordinate of a view according to the screen without using ref. If anyone knows please suggest me it is highly needed to me.

uc-spr commented Aug 16, 2016

How can we get the x, y coordinate of a view according to the screen without using ref. If anyone knows please suggest me it is highly needed to me.

@aleclarson

This comment has been minimized.

Show comment
Hide comment
@aleclarson

aleclarson Aug 16, 2016

Contributor

@uc-spr See this issue: #1374

Contributor

aleclarson commented Aug 16, 2016

@uc-spr See this issue: #1374

@PaulAndreRada

This comment has been minimized.

Show comment
Hide comment
@PaulAndreRada

PaulAndreRada Sep 18, 2016

@brentvatne I'm trying to use measure in ES6 (I'm still a bit new to it) syntax but mixins ( like the NativeMethodsMixin required for your example ) don't seem to be an option on es6. Do you happen to know an alternative?

PaulAndreRada commented Sep 18, 2016

@brentvatne I'm trying to use measure in ES6 (I'm still a bit new to it) syntax but mixins ( like the NativeMethodsMixin required for your example ) don't seem to be an option on es6. Do you happen to know an alternative?

@tobycox

This comment has been minimized.

Show comment
Hide comment
@tobycox

tobycox Sep 23, 2016

Contributor

@PaulAndreRada It's bit uglier, but:

import { findNodeHandle } from 'react-native';
const RCTUIManager = require('NativeModules').UIManager;

RCTUIManager.measure(findNodeHandle(this.refs.myRef), (x, y, width, height, pageX, pageY) => {
      // Got me some dimensions
});

is working for me.

Contributor

tobycox commented Sep 23, 2016

@PaulAndreRada It's bit uglier, but:

import { findNodeHandle } from 'react-native';
const RCTUIManager = require('NativeModules').UIManager;

RCTUIManager.measure(findNodeHandle(this.refs.myRef), (x, y, width, height, pageX, pageY) => {
      // Got me some dimensions
});

is working for me.

@a-koka

This comment has been minimized.

Show comment
Hide comment
@a-koka

a-koka Mar 14, 2017

The suggestion to use UIManager works great on iOS but returns undefined on Android on the latest React-Native version 0.42.0.

a-koka commented Mar 14, 2017

The suggestion to use UIManager works great on iOS but returns undefined on Android on the latest React-Native version 0.42.0.

@facebook facebook locked as resolved and limited conversation to collaborators May 29, 2018

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.