How do I get the vertical offset of a React Native TextInput in the onFocus event? - react-native

I want to get the vertical offset of a React Native TextInput in the onFocus event for that element. I have the following code in a TextInputCustom component I created:
let textInput;
<TextInput
onFocus={(evt) => {
textInput.measure((x, y, width, height, pageX, pageY) => {
console.log('textInput.measure', x, y, width, height, pageX, pageY);
});
}}
ref={(ref) => textInput = ref}
/>
This "works", in that I can see a value for pageY, but the problem is that when I add multiple TextInputCustom components to a screen, it seems to report out the same pageY value for all of them, as if the ref is shared by all of them or something like that. (Honestly, I'm not sure what's going on, but I'm sure that they're all reporting out the same pageY value, even though they're all clearly at differing vertical offsets.)
I was thinking about changing textInput.measure to evt.target.measure, but evt.target seems to be an element ID and not an actual element. I read some stuff on SO about using ReactNativeComponentTree to get the actual element from the element ID, but I can't seem to properly require/import it.
At this point I'm stuck, and looking for any advice on how to properly get the vertical offset of a TextInput element when it's focused on, even when there are multiple TextInput elements on a given screen. Thank you.
Edit: Max, as per your comments, I modified my functional component as follows:
const TextInputCustom = (props) => {
const textInputElem = useRef(null);
<TextInput
onFocus={(evt) => {
textInputElem.current.measure((x, y, width, height, pageX, pageY) => {
console.log('textInput.measure', x, y, width, height, pageX, pageY);
});
}}
ref={textInputElem}
/>
};
I've noticed that the onFocus event fires for the first TextInputCustom component on the screen, but none of the others. Any thoughts? Thank you.

Related

Is there a way to animate the increased size of a View when new children are added?

I’m currently using LayoutAnimation to animate a view when children are added. However, since LayoutAnimation causes everything to be animated, globally, and I can’t easily use built-in Animated library to fit my use-case, I’m wondering if react-native-reanimated is able to help.
Here's a snack of my current solution:
https://snack.expo.io/#insats/height-adapation
This is what the result of that looks like:
Is there a way to achieve the same thing without using LayoutAnimation? I've looked through all exampled in react-native-reanimated, and I've read through the docs but I'm still not sure if this is possible to do or how I should get started. I've thought about using Animated to move the item-wrapper out of the viewable area and "scroll" it upwards (using transform translateY) when items are added, but that would require fixed height, which I don't have.
I have 2 approaches that I can suggest out of my mind:
You can configure your LayoutAnimation only when your desired state changed. If you use hooks it would be too easy:
const [state,setState] = useState([]);
useEffect(()=>{
/*rest code*/
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
},[state])
Or if you use class component you can catch your desired state change in componentDidUpdate:
componentDidUpdate(prevProps,prevState){
if(prevState.items!==state.items){
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
}
}
You can use onLayout function of view:
addItem = () => {
this.setState({
items: [...this.state.items, {title:'An item',isNew:true}]
})
};
renderItems = () => {
return this.state.items.map((item, index) => {
let opacity = new Animated.Value(0);
return (
<Animated.View onLayout={({nativeEvent})=>{
if(this.state.item.isNew){
// here you got the height from nativeEvent.layout.height
// Then you have to store the height animate height and opacity to its precise value
// PS I used opacity:0 to calculate the height
}
}} key={index} style={[styles.item,{opacity}>
<Text>{item.title}</Text>
</View>
)
});
};
When it comes to react-native-reanimated I regard it as more faster version of react-native's Animated library. So either way you will have to calculate the height!

Is it possible to know how many lines of text are rendered by React Native when using numberOfLines prop?

The React Native numberOfLines prop is very useful but I want to programmatically adjust the height of my row between two numbers based on how many lines of text are actually rendered.
For example, I have a Text component of this form <Text numberOfLines={2} ellipsizeMode={'tail'}>{item.text}</Text>
If the text is longer than two lines, it defaults to two lines as desired. But when it is less than two lines, it just shows a single line, again as desired. I just want to know when the content is a single lien versus two lines. Is there any way of finding this out?
possible answer here
herehttps://stackoverflow.com/a/58632169/11816387
I want to provide a modern solution. There is now a onTextLayout event that includes an array of lines which can be determined what number of lines are being rendered. There's other details in the lines array like actual height and width of every line which can be further used to determine if the text is being truncated.
const NUM_OF_LINES = 5;
const SOME_LONG_TEXT_BLOCK = 'Lorem ipsum ...';
function SomeComponent () {
const [ showMore, setShowMore ] = useState(false);
const onTextLayout = useCallback(e =>
setShowMore(e.nativeEvent.lines.length > NUM_OF_LINES);
}, []);
return (
<Text numberOfLines={NUM_OF_LINES} onTextLayout={onTextLayout}>
{SOME_LONG_TEXT_BLOCK}
</Text>
);
}
In React Native, Text component has a props called onLayout
http://facebook.github.io/react-native/docs/text.html#onlayout
with {nativeEvent: {layout: {x, y, width, height}}}
So first, have a state
state = {
numOfLines: 0
}
Then in your Text component
<Text
numberOfLines={this.state.numOfLines}
onLayout={(e) => {
this.setState({ numOfLines: e.nativeEvent.layout.height > YOUR_FONT_SIZE ? 2 : 1 })
}
>
{item.text}
</Text>
I am not totally sure with this solution because I just think it from my mind straight away. But, my logic is if your text height is more than your text fontSize it means that it is more than one line?
Let me know if it is work or not
If you don't mind using an npm package react-native-text-size will solve your problem.
From their docs:
const size = await rnTextSize.measure({
text, // text to measure, can include symbols
width, // max-width of the "virtual" container
...fontSpecs, // RN font specification
})
"size" is not only the size, it has more info, one of which is lineCount, which is what you need.
Personally, I needed to send allowFontScaling: false in the fontSpecs, because we handle that internally.

How to implement, React Native Drag-n-Drop

I want to implement react-native drag-and-drop, to swap component on the drop. supposed there is five component on a screen once I drag it should highlight dropable element. and once it is dropped the component should be swap.
In order to implement drag an drop you need to use the pan responders. First you need to define where are you going to store the values when the object is moving, you can set a property on the state.
state = {
pan: new Animated.ValueXY(),
};
Then you will have to create the pan responders in componentWillMount for example:
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder : () => true,
onPanResponderMove : Animated.event([null,{ // <--- When moving
dx : this.state.pan.x,
dy : this.state.pan.y
}]),
onPanResponderRelease : (e, gesture) => {} // <--- callback when dropped
});
Finally you need to set the left and top to the animated element in the render method.
<Animated.View
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), styles.circle]}>
<Text style={styles.text}>Drag me!</Text>
</Animated.View>
The this.state.pan.getLayout() returns the top and left, you need this in order to move the element around.
In order to swap the elements you need to implement the onPanResponderRelease.
For more detailed steps you can take a look at this tutorial: https://moduscreate.com/blog/animated_drag_and_drop_with_react_native

Set slider to initial position - React native

I have an app in which there is a common slider to display 2 different data values. And a button which controls the values to show (i.e. To use Array1 Or Array2).
Slider is displaying all values properly while doing back-n-forth in slider.
When I click the button, I update the state, Render gets called with correct Array to load from.
Issue: Slider starts from some random position. I want slider head to go to initial position.
Below is my slider code,
<Slider style={styleSliderView.slider}
minimumValue={0}
maximumValue={arr.length-1}
step={1}
onValueChange={(value)=>this._onValueChange(value)}/>
Any thoughts.
This is probably not just a random position. It's the new position at the new min/max range. What you need to do is somehow reset the slider.
You can try 2 possible solutions:
1) save the slider value in your state and reset it when the button is pressed:
<Slider
value={this.state.value}
onValueChange={(value) => this.setState({value})}
/>
onButtonPress() {
this.setState({value: 0});
}
2) reset the value using a ref to the slider:
<Slider
ref={r => this.slider = r}
/>
onButtonPress() {
this.slider.setNativeProps({value: 0});
}

How to find x,y position of Component in React native

How do I find out the x,y position of where an element is positioned on the screen, I'm using React Native v0.14. I have tried the following but it says measure is not a function.
componentDidMount() {
setTimeout(this.measurePosition);
},
measurePosition() {
this.refs.q.measure((a, b, width, height, px,py ) => console.log(width))
},
Option1:
You can use the NativeMethodsMixin. Make sure you've added the mixin to your class
var NativeMethodsMixin = require('NativeMethodsMixin')
...
var MyComponent = React.createClass({
mixins: [NativeMethodsMixin]
...
componentDidMount: function(){
this.refs.element.measure((x,y,w,h,pX,pY) => console.log("dets"))
}
Option 2
You can use the onLayout property on Views. It is available on most components.
render:function(){
...
<View onLayout = {this.onLayout} />
...
},
onLayout:function(event){
console.log(event.nativeEvent.layout)
}
You will get the x,y, width and height
Can you use something like
1. get the browser DOM element using (ReactDOM.findDOMNode())
2. Use the elements getBoundingClientRect()
3. You have top, left, bottom and right coords
this may also be useful Retrieve the position (X,Y) of an HTML element