I've come up with a somewhat contrived way of doing this. Let's look at the problem first.
We can use flexbox to put the "badge" on the left and the text on the right, then have "message rows" going down horizontally. That's easy, but what we want is for the message row to change width depending on its content, and flexbox won't let you do that as it greedily expands to fill all of the space.
What we need is a way of checking the width of the message text then resizing the view accordingly, forcing it to a specified width. We can't use measure
to get the text width since that actually only gives us the width of the underlying node, not the actual text itself.
To do this I stole an idea from here and created a bridge to Obj-C which creates a UILabel with the text and gets its width that way.
// TextMeasurer.h
#import "RCTBridgeModule.h"
#import <UIKit/UIKit.h>
@interface TextMeasurer : NSObject<RCTBridgeModule>
@end
// TextMeasurer.m
#import "TextMeasurer.h"
@implementation TextMeasurer
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(get:(NSString *)text cb:(RCTResponseSenderBlock)callback)
{
UILabel *label = [[UILabel alloc]init];
label.font = [UIFont fontWithName:@"Helvetica" size:14.0];
label.text = text;
callback(@[[NSNumber numberWithDouble: label.intrinsicContentSize.width]]);
}
@end
I then wrapped the usage of this into a component:
var AutosizingText = React.createClass({
getInitialState: function() {
return {
width: null
}
},
componentDidMount() {
setTimeout(() => {
this.refs.view.measure((x, y, width, height) => {
TextMeasurer.get(this.props.children, len => {
if(len < width) {
this.setState({
width: len
});
}
})
});
});
},
render() {
return <View ref="view" style={{backgroundColor: 'red', width: this.state.width}}><Text ref="text">{this.props.children}</Text></View>
}
});
All this will do is resize the containing view if the width of the text is less than the original width of the view - which will have been set by flexbox. The rest of the app looks like this:
var messages = React.createClass({
render: function() {
var rows = [
'Message Text',
'Message Text with lots of content ',
'Message Text with lots of content put in here ok yeah? Keep on talking bla bla bla whatever is needed to stretch this message body out.',
'Keep on talking bla bla bla whatever is needed to stretch this message body out.'
].map((text, idx) => {
return <View style={styles.messageRow}>
<View style={styles.badge}><Text>Me</Text></View>
<View style={styles.messageOuter}><AutosizingText>{text}</AutosizingText></View>
</View>
});
return (
<View style={styles.container}>
{rows}
</View>
);
}
});
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'flex-start',
alignItems: 'stretch',
flexDirection: 'column',
backgroundColor: '#F5FCFF',
},
messageRow: {
flexDirection: 'row',
margin: 10
},
badge: {
backgroundColor: '#eee',
width: 80, height: 50
},
messageOuter: {
flex: 1,
marginLeft: 10
},
messageText: {
backgroundColor: '#E0F6FF'
}
});
AppRegistry.registerComponent('messages', () => messages);
And it gives you this:

I would keep an eye on the Github issue as this solution is definitely a bit clunky and at the very least I'd expect to see a better way of measuring text in RN at some point.