react-native scrollView - scrollToEnd - on Android - react-native

I'm trying to call a function that will fire upon onFoucs on TextInput that will scroll the scrollView all the way down (using scrollToEnd())
so this is my class component
class MyCMP extends Component {
constructor(props) {
super(props);
this.onInputFocus = this.onInputFocus.bind(this);
}
onInputFocus() {
setTimeout(() => {
this.refs.scroll.scrollToEnd();
console.log('done scrolling');
}, 1);
}
render() {
return (
<View>
<ScrollView ref="scroll">
{ /* items */ }
</ScrollView>
<TextInput onFocus={this.onInputFocus} />
</View>
);
}
}
export default MyCMP;
the component above works and it does scroll but it takes a lot of time ... I'm using setTimeout because without it its just going down the screen without calculating the keybaord's height so it not scrolling down enough, even when I keep typing (and triggering that focus on the input) it still doesn't scroll all the way down.
I'm dealing with it some good hours now, I did set the windowSoftInputMode to adjustResize and I did went through some modules like react-native-keyboard-aware-scroll-view or react-native-auto-scroll but none of them really does the work as I need it.
any direction how to make it done the right way would be really appreciated. thanks!

Rather than using a setTimeout you use Keyboard API of react-native. You add an event listener for keyboard show and then scroll the view to end. You might need to create some logic on which input is focused if you have more than one input in your component but if you only have one you can just do it like the example below.
Another good thing to do is changing your refs to functional ones since string refs are considered as legacy and will be removed in future releases of react. More info here.
class MyCMP extends Component {
constructor(props) {
super(props);
this.scroll = null;
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow.bind(this));
}
componentWillUnmount () {
this.keyboardDidShowListener.remove();
}
_keyboardDidShow() {
this.scroll.scrollToEnd();
}
render() {
return (
<View>
<ScrollView ref={(scroll) => {this.scroll = scroll;}}>
{ /* items */ }
</ScrollView>
<TextInput />
</View>
);
}
}
export default MyCMP;

If you have a large dataset React Native docs is telling you to go with FlatList.
To get it to scroll to bottom this is what worked for me
<FlatList
ref={ref => (this.scrollView = ref)}
onContentSizeChange={() => {
this.scrollView.scrollToEnd({ animated: true, index: -1 }, 200);
}}
/>

Related

React Native components seem to be sharing a state

I'm having an issue with React-native where I have a component TouchTimer which uses an AnimatedTimer component. This timer is supposed to start and stop when it is tapped, which it does, however all of the TouchTimer components I add to a page will start and stop whenever any of them are tapped, rather than only affecting the tapped component.
Below is a snippet of my component:
TouchTimer.tsx
export class TouchTimer extends React.Component<TouchTimerProps> {
state: {
...
paused: boolean,
}
constructor(props) {
super(props);
...
this.state = {
...
paused: true,
}
}
startStop() {
this.setState({paused: !this.state.paused});
}
render() {
const { time } = this.props;
return (
<TouchableHighlight onPress={() => this.startStop()}>
<View>
<AnimatedTimer
...
time={time}
pause={this.state.paused}
/>
<View style={styles.timeContainer}>
<Text style={styles.time}>{this.state.remaining}</Text>
</View>
</View>
</TouchableHighlight>
)
}
}
And here is a snippet of the screen containing these components:
Details.tsx
import { TouchTimer } from '../components/TouchTimer';
...
export class RecipeDetailsScreen extends React.Component<NavigationInjectedProps> {
...
{this.state.steps.map(step => (
<List.Item
key={step.id}
title={"Step " + step.index}
style={styles.step}
description={step.short_desc}
right={() => (step.time > 0 &&
<TouchTimer
time={step.time * 60000}
/>
)}
/>
)
}
I have tried wrapping the TouchTimer components in a View and changing the paused boolean to a prop, to no avail.
I have also tested to see if this issue appears when the components are not siblings, and when they are not produced as the result of a callback, and the issue still persists in both these cases.
If anybody has any advice or answers on how to make these timers independent I would very much appreciate it!
Curiously that component seems to be implemented with a global pauseFlag that applies to all component instances. See https://github.com/dalisalvador/react-native-animated-timer/blob/master/src/Components/AnimatedTimer.js#L34
So I don't think you're doing anything wrong here, this is a limitation of the library code that is coupling all instances of your timer to the same pauseFlag value.

React Native ios Switch in FlatList not toggling after value changed

I am trying to toggle ios Switch in react native. But the switch comes back to initial position as soon as I change it.
What I have:
class ABC extends Component {
constructor(props) {
super(props)
this.state = {
obj: []
}
}
fetch(){
// fetch something from remote server, set it to state object array
}
setStatus(id, value){
var temp = [...this.state.obj]
temp.map((t) => {
if (t.id == id) {
t.flag = value
}
})
this.setState({ obj: temp })
}
render() {
return (
<View>
<FlatList
data={this.state.obj}
renderItem={({ item }) =>
<View>
<Text>{item.name}</Text>
<Switch
onValueChange={(val) => this.setStatus(item.id, val)}
value={item.flag}
/>
</View>
}
keyExtractor={({ id }, index) => id.toString()}
/>
</View>
);
}
}
I logged the before and after value of obj state and they seem to update. Should the FlatList be rendered again (like a web page refresh) ? Or is there something I am missing ? Searched SO for answers, couldn't find my mistake.
Flatlist has a prop called extraData.
This prop tells Flatlist whether to re-render or not.
If data in extraData changes then flatlist re-renders based on new data provided in data prop.
So whenever you need to re-render flatlist just change something in extraData.
Best way is to pass state toextraData which is passed to Data.
So, just pass extraData={this.state.obj}.
there also other way called forceUpdate.
you can call this.forceUpdate().
but this is not recommended because this will render not only flatlist but entire component in which you are calling this.

Getting the Id of a react-native element in event handler

How to get the Id of the element in onPress event handler.
I am adding elements dynamically and wants to know in the event handler of onPress of these elements to store in the state which elements are clicked.
Here is the code i have
export default class App extends Component {
constructor(props){
super(props);
this.getElements= this.getElements.bind(this);
this.selectElement = this.selectElement.bind(this);
}
componentWillMount(){
this.state = {
noOfElements :10
}
}
selectElement(e,key){
console.log('selectElement() : key=',key);
}
getElements(){
let elements =[];
for(let index=0;index<this.state.noOfElements;index++){
elements.push(
<View key={'View_'+index} style={{flex:1}}>
<Button
key={'View_'+index}
id={index}
onPress={(e,index) => {this.selectElement(e,index)}}
title={'Button-'+index}
/>
</View>
);
}
return elements;
}
render(){
let elements = this.getElements();
return(
<View style={styles.container}>
<Text>Test</Text>
{elements}
</View>
);
}
}
I tried just passing the key like
onPress={(index) => {this.selectElement(index)}}
with no success..
Not sure what i am doing wrong.
The way you have it, i think index would come up undefined, just remove index as an argument in your onPress so it grabs index from the for loop. Also you can prob refactor it using map.
onPress={(e) => this.selectElement(e,index)}
Changed the event handler as below and it is working fine now.
onPress={this.selectElement.bind(this,index)}
and the function now just accepts the index
selectElement(key){
console.log('selectElement() : Index=',key);
}

React Native Navigator: Can I remove navigation gestures from a scene after a user action?

I want to disable the swipe from left pop gesture on the navigator after the side menu has been accessed within a scene. I don't want to disable it when the scene first renders, only when the side menu is open. I have an onOpen function I can call, but I don't know how to programatically change the navigation gestures without pushing another route.
I tried setting the configureScene prop of the navigator like this:
configureScene={() => {
return this.state.swipeBackNavigation ? FloatFromRight : Navigator.SceneConfigs.FloatFromRight;
}
and changing the state, but the component doesn't rerender
ideas would be appreciated
I believe you can just set gestures to null (effectively disabling it):
gestures: {}
I can't test this currently, but I suspect it will work (if I didn't screw up some syntax somewhere):
export default class Foo extends Component {
constructor(props){
super(props);
this.state = {
//initialize gestureChoice
gestureChoice: {},
}
}
disablePop(){
setState({ gestureChoice:{ gestures:{} } });
}
enablePop(){
setState({gestureChoice: ...Navigator.SceneConfigs.FloatFromRight});
}
render(){
return(
<Navigator
renderScene={(route, navigator) =>
return <SomeScene navigator={navigator} {...route.passprops} />
}
configureScene={(route, routeStack) =>
this.state.gestureChoice;
)}
/>
);
}
}
The idea being, you could use enablePop() and disablePop() whenever you would like.
This thread is probably helpful: https://github.com/facebook/react-native/issues/1014

React Native: Updating state in onLayout gives "Warning: setState(...): Cannot update during an existing state transition"

I have a component in React Native which updates it's state once it knows what size it is.
Example:
class MyComponent extends Component {
...
render() {
...
return (
<View onLayout={this.onLayout.bind(this)}>
<Image source={this.state.imageSource} />
</View>
);
}
onLayout(event) {
...
this.setState({
imageSource: newImageSource
});
}
...
}
This gives the following error:
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
I guess the onLayout function is called while still rendering (which can be good, the sooner the update, the better). What is the correct way to solve this problem?
Thanks in advance!
We got around this by using the measure function, you will have to wait until the scene is fully complete before measuring to prevent incorrect values (i.e. in componentDidMount/componentDidUpdate). Here's an example:
measureComponent = () => {
if (this.refs.exampleRef) {
this.refs.exampleRef.measure(this._logLargestSize);
}
}
_logLargestSize = (ox, oy, width, height, px, py) => {
if (height > this.state.measureState) {
this.setState({measureState:height});
}
}
render() {
return (
<View ref = 'exampleRef' style = {{minHeight: this.props.minFeedbackSize}}/>
);
}
Here is a solution from documentation for such cases
class MyComponent extends Component {
...
render() {
...
return (
<View>
<Image ref="image" source={this.state.imageSource} />
</View>
);
}
componentDidMount() {
//Now you can get your component from this.refs.image
}
...
}
But for my opinion it's better to do such things onload