FlatList Ref is Incorrect - javascript

I'm having an issue with the FlatList component when calling scrollToIndex. Here is how I'm referencing the list.
<Modal visible={modal} transparent onRequestClose={this.onClose}>
<TouchableWithoutFeedback onPress={this.onClose}>
<View style={overlayStyle}>
<Animated.View style={[styles.picker, pickerStyle]}>
<FlatList
ref={(node) => { this.scroll = node; }}
style={styles.scroll}
data={data}
renderItem={this.renderItems}
getItemLayout={(_, index) => (
{ length: 24, offset: 24 * index, index }
)}
initialNumToRender={5}
// scrollEnabled={visibleItemCount < itemCount}
contentContainerStyle={styles.scrollContainer}
keyExtractor={(item) => item.value}
automaticallyAdjustContentInsets={false}
removeClippedSubviews={false}
indicatorStyle="white"
/>
</Animated.View>
</View>
</TouchableWithoutFeedback>
</Modal>
I included the surrounding code in case that would help. But for some reason I get back a different structure than normal with an error that says _scrollRef.scrollToIndex is undefined. When I check the tree and the ref component I seem to get back the right structure of _listRef -> _scrollRef but the contents are of that of a view ref or a textinput ref. Looking something like...
blur
context
focus
measure
measureInWindow
measureLayout
props
refs
setNativeProps
state
updater
I'm extremely confused on this situation and some insight as to why would be great.
EDIT:
Here is the code for the scrollToIndex. The promise method was recommended by someone in the RN community. I was using just a set timeout previously.
const wait = new Promise((resolve) => setTimeout(resolve, 500));
wait.then(() => {
if (this.mounted) {
console.log(this.scroll);
if (this.scroll) {
this.scroll
.scrollToIndex({ index: 0, animated: false });
}
Animated
.timing(opacity, {
duration: animationDuration,
toValue: 1,
})
.start(() => {
// if (this.mounted && Platform.OS === 'ios') {
// this.scroll.flashScrollIndicators();
// }
});
}
});

Related

How to use useAnimatedStyle inside a FlatList renderItem?

im trying to create a carousel effect upon scrolling using renimatedV2 and im realizing that because of the useAnimatedStyle hook dependency I cannot apply the animated style over to the view. Reason is it is a hook and I cannot place it inside the renderItem. The reason I need to place it inside the renderItem is because the interpolation depends on the index of the item. Is there a work around for this? surely the very amazing people at software mansion thought about this while creating renimatedV2 but I just cant find the solution.
const animatedScale = useSharedValue(1)
const animatedScaleStyle = useAnimatedStyle(() => {
return {
transform: [
{
scale: animatedScale.value,
},
],
}
})
const renderItem = useCallback(({ item, index }) => {
const inputRange = [-1, 0, 210 * index, 210 * (index + 0.5)]
const scale = interpolate(animatedScale.value, inputRange, [1, 1, 1, 0])
return (
<Animated.View
style={{
height: 200,
marginBottom: 10,
transform: [
{
scale: scale,
},
],
}}
>
<ThumbnailBig
ref={thumbnailRef}
images={item}
key={item.id}
oneEllipsisPressed={oneEllipsisPressed.bind(this, item.id)}
/>
</Animated.View>
)
}, [])
const onScroll = useAnimatedScrollHandler((event, context) => {
const { y } = event.contentOffset\
animatedScale.value = y
})
return (
<AnimatedFlatList
ref={bigListRef}
data={image}
renderItem={render}
keyExtractor={keyExtractor}
onScrollEndDrag={handleScroll}
initialNumToRender={5}
maxToRenderPerBatch={5}
initialScrollIndex={scrollIndex}
onScrollToIndexFailed={scrollFailed}
windowSize={4}
contentContainerStyle={{
paddingBottom: 40,
}}
alwaysBounceVertical={false}
bounces={false}
onScroll={onScroll}
scrollEventThrottle={16}
extraData={refreshFlatlist}
style={styles.flatList}
/>
)
I got the same problem and I finally managed to solve it by changing how renderItem is passed.
You need to change from
renderItem={renderitem}
to this
renderItem={({item,index}) => <Item item={item} index={index} />}
and from
const renderItem = useCallback(({ item, index }) => {
to this
const Item = useCallback(({ item, index }) => {
The reason you can't use useAnimatedStyle in renderItem is because it is not a Functional Component.
So instead of passing renderItem directly to the FlatList, you need to convert it into a Functional Component so that you can use hooks. I hope this helps :)

Delay style display when using Pressable React Native component

I'm using the Pressable React Native component for items displayed in a FlatList.
I want to be able to scroll back and forth through the list and have no feedback from the items, unless pressed for a a little while.
The onPress function invoked can easily be delayed with the onLongPress capability, however I also want to invoke an opacity over the item after it's been pressed for a little while, NOT during scrolling. There doesn't seem to be an easy way to do this. What I've tried so far without succes:
.........
const sleep = (milliseconds: any) => {
return new Promise(resolve => setTimeout(resolve, milliseconds));
};
const display = (pressed: boolean) => {
if (pressed) {
sleep(3000).then(() => {
return true;
});
}
return false;
};
const ItemInList: FunctionComponent<ItemInListProps> = ({
style,
colors,
title = '',
text,
subtext,
children,
onPress,
}) => {
return (
<Pressable
onLongPress={onPress}
delayLongPress={3000}
style={({ pressed }) => [
{
opacity: display(pressed) ? 0.2 : 1,
},
]}>
<LinearGradient
colors={colors || []}
style={StyleSheet.flatten([styles.container, style])}>
<View style={styles.titleContainer}>
<Text style={styles.titleStyle}>{title}</Text>
</View>
<View style={subtext ? styles.subtextContainer : styles.textContainer}>
<Text style={styles.textStyle}>{text}</Text>
</View>
{subtext && (
<View style={styles.subtextContainer}>
<Text style={styles.subtextStyle}>{subtext}</Text>
</View>
)}
{children}
</LinearGradient>
</Pressable>
);
};
export default ItemInList;
This has no effect whatsoever, opacity is never displayed.
Does anyone have a good idea about how to handle this?
Thanks.
Can you try TouchableOpacity? it has props delayPressIn and many props u can try these
I'm pretty sure that when the OP has asked this question, there was no straightforward solution.
Right now you can use the "unstable_pressDelay" provided prop to define a number of milliseconds to delay the pressable activation.
Example code:
<Pressable
unstable_pressDelay={5000}
onPress={() => {
setTimesPressed((current) => current + 1);
}}
style={({ pressed }) => [
{
backgroundColor: pressed
? 'rgb(210, 230, 255)'
: 'white'
},
styles.wrapperCustom
]}>
{({ pressed }) => (
<Text style={styles.text}>
{pressed ? 'Pressed!' : 'Press Me'}
</Text>
)}
</Pressable>
Documentation: https://reactnative.dev/docs/pressable#unstable_pressdelay

How would I dynamically append duplicate components in react, react-native

I am confused about how to properly dynamically add/create same components on button press for react native. I have used .map(()=>{}) on existing info to create components and then display the results.
Would I have to save each new component into a setstate array, then map that?
I looked a little into refs, but wasn't sure how that was better than just a setstate. The problem I see is if I want to update the value for each component, how would I go about that if their all originally duplicates?
Something along the lines of this:
class SingleExercise extends Component {
constructor(props) {
super(props);
this.state = {
objInfo: this.props.navigation.getParam("obj"),
currentSetSelected: 0
};
this.addSet = this.addSet.bind(this);
}
addSet = () => {
return (
<addSet />
)
}
render() {
const { navigation } = this.props;
return (
<View style={{ flex: 1 }}>
<View style={{ height: 80 }}>
<addSet />
<View>
<Button //here
large={false}
onPress={() => {
this.addSet();
}}
title={"add more"}
/>
</View>
</View>
</View>
);
}
}
const addSet = () => {
return (
<TouchableHighlight>
<View>
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
defaultValue={'test'}
onChangeText={(text) => this.setState({text})}
/>
</View>
</TouchableHighlight>
);
}
Here is what I would do:
Every click on addSet button should increment the AddSets counter like this:
<Button
large={false}
onPress={() => {
this.setState({addSetsCounter: this.state.addSetsCounter});
}}
title={"add more"}
/>
After every state update your component will be re-rendered. So now, all you need to do is to forLoop in through that counter and return as many AddSets components as needed. A simple for loop with .push() inside would do.
Inside render, before return place something like that:
let sets =[];
for(let i =0;i<this.state.addSetsCounter;i++){
sets.push(<AddSets key="AddSets-{i}"/>);
}
Then simply render your {sets}.
I cannot fully test that right now, I wrote that from the top of my head, just play with the above, at least I hope it points you in a right direction.

react-native: Updating an element in the setstate array does not re-render the component

I was trying to update an array in setstate and want to render child component based on the update.
Initial state:
state={selectAdress: []};
componentDidMount:
const selectAdress = [];
if (addresses.length > 0) {
for (i = 0; i < addresses.length; i++) {
selectAdress.push(false);
}
this.setState({ selectAdress: selectAdress });
}
Method:
const selectAdress = this.state.selectAdress.slice();
for (i = 0; i < selectAdress.length; i++) {
if (i === index) {
selectAdress[index] = !this.state.selectAdress[index];
}
else {
selectAdress[i] = false;
}
}
this.setState({ selectAdress: selectAdress }, () => {
console.log(this.state, '787878787878778787')
});
Render code in the main component:
render() {
const { selectAdress } = this.state
console.log(this.state);
return (
<AddressComponent
updateAddressCheckbox={this.updateAddressCheckbox}
onChangeText={this.onChangeText}
selectAdress={selectAdress} />
);
}
Render Code in the child component:
const selectAdress = propsObj.selectAdress;
return (
<View style={{ height: SCREEN_HEIGHT - 85 }}>
<FlatList
data={addresses}
keyExtractor={(x,i) => i.toString()}
renderItem={({ item, index }) => <View style={styles.container}>
<CheckBox
title={null}
checkedIcon='check-square-o'
uncheckedIcon='square-o'
checked={selectAdress[index]}
onPress={() => propsObj.updateAddressCheckbox(index)} />
I tried many answers from stack overflow. But nothing is allowing me to re-render the component. However, the state is clearly updating if i check the logs.
can someone help me out with what am I doing wrong here?
From your code, it seems that you're pulling the info for selectAdress from props instead of state:
const selectAdress = propsObj.selectAdress;
You should try changing it to use this instead:
const { selectAdress } = this.state;
Side-note: address should be spelt with 2 d's. You did this correctly in other variables (e.g. updateAddressCheckbox) so you should update it to selectAddress for consistency.
By Adding extraData property of FlatList, i was able to resolve the issue.
return (
<View style={{ height: SCREEN_HEIGHT - 85 }}>
<FlatList
extraData={propsObj}
data={addresses}
keyExtractor={(x,i) => i.toString()}
renderItem={({ item, index }) => <View style={styles.container}>
<CheckBox
title={null}
checkedIcon='check-square-o'
uncheckedIcon='square-o'
checked={selectAdress[index]}
onPress={() => propsObj.updateAddressCheckbox(index)} />
Since selectAdress is not part of the data (addresses), you need to add extraData={propsObj.selectAdress}.

How to autofocus next TextInput on react-native

I'm trying to create a passcode protected screen. The screen will uses 4 numeric input as the passcode.
The way I'm doing this is create a TextInput Component and call it 4 times in my main screen.
The problem I'm having is the TextInputs will not focus on the next one as I type the value of the previous TextInput.
I'm using refs for all PasscodeTextInput component (I've been informed that it is a legacy method but I do not know any other way, alas).
Tried this method(without creating my own component), no luck too.
METHOD
index.ios.js
import React, { Component } from 'react';
import { AppRegistry, TextInput, View, Text } from 'react-native';
import { PasscodeTextInput } from './common';
export default class ProgressBar extends Component {
render() {
const { centerEverything, container, passcodeContainer, textInputStyle} = styles;
return (
<View style={[centerEverything, container]}>
<View style={[passcodeContainer]}>
<PasscodeTextInput
autoFocus={true}
ref="passcode1"
onSubmitEditing={(event) => { this.refs.passcode2.focus() }} />
<PasscodeTextInput
ref="passcode2"
onSubmitEditing={(event) => { this.refs.passcode3.focus() }} />
<PasscodeTextInput
ref="passcode3"
onSubmitEditing={(event) => { this.refs.passcode4.focus() }}/>
<PasscodeTextInput
ref="passcode4" />
</View>
</View>
);
}
}
const styles = {
centerEverything: {
justifyContent: 'center',
alignItems: 'center',
},
container: {
flex: 1,
backgroundColor: '#E7DDD3',
},
passcodeContainer: {
flexDirection: 'row',
},
}
AppRegistry.registerComponent('ProgressBar', () => ProgressBar);
PasscodeTextInput.js
import React from 'react';
import {
View,
Text,
TextInput,
Dimensions
} from 'react-native';
const deviceWidth = require('Dimensions').get('window').width;
const deviceHeight = require('Dimensions').get('window').height;
const PasscodeTextInput = ({ ref, autoFocus, onSubmitEditing, onChangeText, value}) => {
const { inputStyle, underlineStyle } = styles;
return(
<View>
<TextInput
ref={ref}
autoFocus={autoFocus}
onSubmitEditing={onSubmitEditing}
style={[inputStyle]}
maxLength={1}
keyboardType="numeric"
placeholderTextColor="#212121"
secureTextEntry={true}
onChangeText={onChangeText}
value={value}
/>
<View style={underlineStyle} />
</View>
);
}
const styles = {
inputStyle: {
height: 80,
width: 60,
fontSize: 50,
color: '#212121',
fontSize: 40,
padding: 18,
margin: 10,
marginBottom: 0
},
underlineStyle: {
width: 60,
height: 4,
backgroundColor: '#202020',
marginLeft: 10
}
}
export { PasscodeTextInput };
Update 1
index.ios.js
import React, { Component } from 'react';
import { AppRegistry, TextInput, View, Text } from 'react-native';
import { PasscodeTextInput } from './common';
export default class ProgressBar extends Component {
constructor() {
super()
this.state = {
autoFocus1: true,
autoFocus2: false,
autoFocus3: false,
autoFocus4: false,
}
}
onTextChanged(t) { //callback for immediate state change
if (t == 2) { this.setState({ autoFocus1: false, autoFocus2: true }, () => { console.log(this.state) }) }
if (t == 3) { this.setState({ autoFocus2: false, autoFocus3: true }, () => { console.log(this.state) }) }
if (t == 4) { this.setState({ autoFocus3: false, autoFocus4: true }, () => { console.log(this.state) }) }
}
render() {
const { centerEverything, container, passcodeContainer, testShit, textInputStyle } = styles;
return (
<View style={[centerEverything, container]}>
<View style={[passcodeContainer]}>
<PasscodeTextInput
autoFocus={this.state.autoFocus1}
onChangeText={() => this.onTextChanged(2)} />
<PasscodeTextInput
autoFocus={this.state.autoFocus2}
onChangeText={() => this.onTextChanged(3)} />
<PasscodeTextInput
autoFocus={this.state.autoFocus3}
onChangeText={() => this.onTextChanged(4)} />
<PasscodeTextInput
autoFocus={this.state.autoFocus4} />
</View>
</View>
);
}
}
const styles = {
centerEverything: {
justifyContent: 'center',
alignItems: 'center',
},
container: {
flex: 1,
backgroundColor: '#E7DDD3',
},
passcodeContainer: {
flexDirection: 'row',
},
}
AppRegistry.registerComponent('ProgressBar', () => ProgressBar);
There is a defaultProp for TextInput where one can focus after component mounted.
autoFocus
If true, focuses the input on componentDidMount, the default value is false. for more information please read the related Docs.
UPDATE
After componentDidUpdate it won't work properly. In that case, one can use ref to focus programmatically.
You cannot forward the ref to <TextInput> using that way because ref is one of the special props. Thus, calling this.refs.passcode2 will return you <PasscodeTextInput> instead.
Try change to the following to get the ref from <TextInput>.
PasscodeTextInput.js
const PasscodeTextInput = ({ inputRef, ... }) => {
...
return (
<View>
<TextInput
ref={(r) => { inputRef && inputRef(r) }}
...
/>
</View>
...
);
}
Then, assign the inputRef from <PasscodeTextInput> to a variable and use focus() to switch focus (it is not deprecated as of RN 0.41.2).
index.ios.js
return (
<PasscodeTextInput
autoFocus={true}
onChangeText={(event) => { event && this.passcode2.focus() }} />
<PasscodeTextInput
inputRef={(r) => { this.passcode2 = r }}
onChangeText={(event) => { event && this.passcode3.focus() }} />
<PasscodeTextInput
inputRef={(r) => { this.passcode3 = r }}
onChangeText={(event) => { event && this.passcode4.focus() }} />
<PasscodeTextInput
inputRef={(r) => { this.passcode4 = r }} />
);
P.S: event && this.passcode2.focus() prevents focus is switched when trying to clear the old passcode and enter a new one.
we handled this style of screen with a different approach.
Rather than manage 4 individual TextInputs and handle the navigation of focus across each one (and then back again when the user deletes a character), we have a single TextInput on screen but is invisible (ie. 0px x 0px) wide which has the focus, maxLength and keyboard configuration, etc.
This TextInput takes input from the user but can't actually been seen, as each character is typed in we render the entered text as a series simple View/Text elements, styled much similar to your screen above.
This approach worked well for us with no need to manage what the 'next' or 'previous' TextInput to focus next to.
You can use focus method onChangeText as Jason stated, in addition to that adding maxLength={1} can make you jump to the next input immediately without checking what's added. (just noticed its deprecated, but still this is how I solved my problem, and should do fine until v0.36, and this link explains how you should update the deprecated function).
<TextInput
ref="first"
style={styles.inputMini}
maxLength={1}
keyboardType="numeric"
returnKeyType='next'
blurOnSubmit={false}
placeholderTextColor="gray"
onChangeText={(val) => {
this.refs['second'].focus()
}}
/>
<TextInput
ref="second"
style={styles.inputMini}
maxLength={1}
keyboardType="numeric"
returnKeyType='next'
blurOnSubmit={false}
placeholderTextColor="gray"
onChangeText={(val) => {
this.refs['third'].focus()
}}
/>
...
Please notice that my use of refs are deprecated too, but I've just copied the code since I can guarantee you that was working back then (hopefully works now too).
Finally, the main issue with this type of implementation is, once you try to remove a number with backspace your focus will jump to next one, causing serious UX issues. However, you can listen for backspace key entry and perform something different instead of focusing to next input. So I'll leave a link here for you to further investigate if you choose to use this type of implementation.
Hacky Solution to Previously Described Issue: If you check what's entered in onChangeText prop before doing anything, you can jump to next input if the value is a number, else (that's a backspace), jump back. (Just came up with this idea, I haven't tried it.)
I think the issue is that onSubmitEditing is when you hit the "return" or "enter" key on the regular keyboard... there is not one of those buttons on the keypad.
Assuming you want each input to only have one character, you could look at the onChangeText and then check if text has length 1 and call focus if the length is indeed 1.
<TextInput
ref={input => {
this.nameOrId = input;
}}
/>
<TouchableOpacity
onPress={()=>{
this.nameOrId.focus()
}}
>
<Text>Click</Text>
</TouchableOpacity>
I solve with this code:
const VerifyCode: React.FC = ({ pass, onFinish }) => {
const inputsRef = useRef<Input[] | null[]>([]);
const [active, setActive] = useState<number>(0);
const onKeyPress = ({ nativeEvent }:
NativeSyntheticEvent<TextInputKeyPressEventData>) => {
if (nativeEvent.key === "Backspace") {
if (active !== 0) {
inputsRef.current[active - 1]?.focus();
return setActive(active - 1);
}
} else {
inputsRef.current[active + 1]?.focus();
return setActive(active + 1);
}
return null;
};
return (
<View style={styles.container}>
<StyledInput
onKeyPress={onKeyPress}
autoFocus={active === 0}
ref={(r) => {
inputsRef.current[0] = r;
}}
/>
<StyledInput
onKeyPress={onKeyPress}
autoFocus={active === 1}
ref={(r) => {
inputsRef.current[1] = r;
}}
/>
<StyledInput
onKeyPress={onKeyPress}
autoFocus={active === 2}
ref={(r) => {
inputsRef.current[2] = r;
}}
/>
<StyledInput
onKeyPress={onKeyPress}
autoFocus={active === 3}
ref={(r) => {
inputsRef.current[3] = r;
}}
/>
</View>
);
};
export default VerifyCode;
I put one ref in all the inputs, and when the onKeyPress fire, the function verify if have to go back or go to next input
Solved it by removing autoFocus={true} and setting timeout.
I have a popup as a functional component and using "current.focus()" with Refs like this:
const Popup = ({ placeholder, autoFocus, showStatus, }) => { const inputRef = useRef(null); useEffect(() => {
Platform.OS === 'ios'
? inputRef.current.focus()
: setTimeout(() => inputRef.current.focus(), 40); }, [showStatus]); return (
<View style={styles.inputContainer}>
<TextInput
style={styles.inputText}
defaultValue={placeholder}
ref={inputRef}
/>
</View> };

Categories