How to use the same ref between child/parent component - reactjs

I am trying to control the carousel in the child component from the parent component.
I have used forward ref on the child component but its not working. Where am I going wrong?
Parent:
const CoachingCarousel = ({}) => {
const carouselRef = useRef<Lottie>(null);
const renderItem = ({item}: any) => {
return (
<View style={styles.renderItemContainer}>
{item.icon}
<Text style={[styles.titletext, spacing.gbMt7]} variant="titleLarge">
{item.title}
</Text>
<Text style={[styles.subtitleText, spacing.gbMt4]} variant="bodyMedium">
{item.text}
</Text>
<Text
style={[styles.next]}
variant="bodyLarge"
onPress={() =>
carouselRef?.current?.goToSlide(
totalSlides !== item.key
? item.key
: () => {
setCoachingScreenCompleted('CoachingScreenCompleted', true),
console.log('Go to homepage');
},
)
}>
{totalSlides !== item.key ? 'Next tbc' : 'Done tbc'}
</Text>
</View>
);
};
return (
<AppCarousel slides={slides} renderItem={renderItem} ref={carouselRef} />
);
};
Child:
const AppCarousel = React.forwardRef(
({style, slides, renderItem}: props, ref) => {
return (
<View style={[styles.container, style]}>
<AppIntroSlider
ref={ref}
renderItem={renderItem}
data={slides}
/>
</View>
);
},
);

Here a is React rule,
1. Do not declare components within other components, this will lead to very weird behaviors in React.
2. Also you cannot share ref between two components at the same time whether they are parent/child or siblings.
Answer
Also from what I am seeing, you are using a ref to keep track of the current slide. ref does not rerender in React so you will not see your changes. Try using useState.
useRef is for non rerendering values or to keep track of DOM nodes which is not necessary in your case.
I found this tutorial to be quite well. One critic would be the over complicated use of React.children.map and React.children.clone although they have their use cases.

Related

React Native: How does usage of nested components affect performance?

Let's say I have a FlatList component that renders items whose counts can be anywhere between 10 to 100+.
The official doc on FlatList optimization says "Try to avoid a lot of logic and nesting in your list items." I want to understand better what this actually means.
For the item components, I have a couple of options (Examples are simplified, the actual components are much more complicated obviously):
Option 1: Using a nested structure, e.g. :
const ItemComponent = (props) => {
const ChildComponent1 = (props) => {
return (
<View>
<Image source={props.image}/>
</View>
)
}
const ChildComponent2 = () => {
return (
<View>
<Text>This is Child Component.</Text>
</View>
)
}
return (
<View>
<Text>This is {props.name} </Text>
<ChildComponent1 image={props.image}/>
<ChildComponent2/>
</View>
)
}
Option 2: Using a non-nested structure, e.g. :
const ItemComponent = (props) => {
return (
<View>
<Text>This is {props.name} </Text>
<View>
<Image source={props.image}/>
</View>
<View>
<Text>This is Child Component.</Text>
</View>
</View>
)
}
Am I actually better off in terms of performance if I use option 2?
If so, how much performance gain will I have and why? I'd like to understand deeper, but cannot find a resources that explains the rationale in detail.
Thanks!

React Native TextInput onFocus does not allow onPress of other child components

Nested TextInput component does not allow other components' onPress function to be called. Only when the TextInput is not focused, the onPress works fine.
React Native Version : 0.66.3
Here is my code
export const Component = (props): JSX.Element {
const { searchText, onChangeSearchText, onClearSearchText } = props
const [searchViewFocused, setSearchViewFocused] = useState<boolean>(false)
const searchIcon = searchViewFocused ? "searchActive" : "searchInactive"
const cancelIcon = searchViewFocused ? "cancelActive" : "cancelInactive"
return (
<View style={styles.searchBoxContainer}>
<View style={[styles.searchBox, searchViewFocused && styles.searchBarActive]}>
<Icon styles={styles.searchIcon} icon={searchIcon} />
<TextInput
style={styles.searchInput}
onChangeText={onChangeSearchText}
value={searchText}
onFocus={() => setSearchViewFocused(true)}
onBlur={() => setSearchViewFocused(false)}
autoCompleteType={"off"}
numberOfLines={1}
/>
{searchText !== "" && (
<Pressable style={styles.clearIcon} onPress={onClearSearchText}>
<Icon icon={cancelIcon} />
</Pressable>
)}
</View>
</View>
)
})
Attached are the images of
Expected.
VS
The issue
When the cross icon is pressed on focused state, the textInput is unfocused rather What I am trying to achieve is that the search text gets cleared & the input remains focused.
Note: The onPress works perfectly fine when input is not focused
Any help is appreciated. Thanks!
PS: Tried TouchableOpacity & also I have tried wrapping the components inside a ScrollView to use keyboardShouldPersistTaps='handled' as mentioned in one of the SO answer but to no success.
Found a workaround to this is to wrap the whole component into a ScrollView and adding the prop keyboardShouldPersistTaps='handled'
Previously I was making the View inside the Component as ScrollView and adding keyboardShouldPersistTaps='handled' which did not work
export const Component = (props): JSX.Element {
...
return (
<ScrollView contentContainerStyle={styles.searchBoxContainer}
keyboardShouldPersistTaps='handled'>
...
</ScrollView>
)
})
The key was to wrap the entire component inside the ScrollView,
Here's what worked:
<ScrollView keyboardShouldPersistTaps='handled'>
<Component {...props}/>
</ScrollView>
Guess this was a silly mistake, but worth pointing out!
You're setting the focus of the entire component based on whether the TextInput has focus. Since the clear button is outside the text input, pressing it causes the component to lose focus.
One solution is to store the TextInput instance in a ref. When the clear button is pressed, you can refocus the text input. I've copied your component below and added some new lines, which are marked in comments.
export const Component = (props): JSX.Element {
const { searchText, onChangeSearchText, onClearSearchText } = props
const textInputRef = useRef(null); // new
const [searchViewFocused, setSearchViewFocused] = useState<boolean>(false)
const searchIcon = searchViewFocused ? "searchActive" : "searchInactive"
const cancelIcon = searchViewFocused ? "cancelActive" : "cancelInactive"
const onClear = () => {
onClearSearchText();
textInputRef.current?.focus();
} // new
return (
<View style={styles.searchBoxContainer}>
<View style={[styles.searchBox, searchViewFocused && styles.searchBarActive]}>
<Icon styles={styles.searchIcon} icon={searchIcon} />
<TextInput
ref={textInputRef} // new
style={styles.searchInput}
onChangeText={onChangeSearchText}
value={searchText}
onFocus={() => setSearchViewFocused(true)}
onBlur={() => setSearchViewFocused(false)}
autoCompleteType={"off"}
numberOfLines={1}
/>
{searchText !== "" && (
<Pressable style={styles.clearIcon} onPress={onClear}> // calls new function
<Icon icon={cancelIcon} />
</Pressable>
)}
</View>
</View>
)
})

Will memoization prevent my React component from rerendering?

I'm struggling to understand what causes my component to rerender as there is no state present here. Code to focus on is Flatlist as this is React Native but this is about React and not specifically RN. As you can see inside Flatlist I render renderPost. I know that renderPost, aka reusable component PostListItem rerenders because this console.log you can see inside flatlist gives me repeated keys, and even console logging postsSortedByDate shows me that they render few times. Because of this, I even have a warning child list keys must be unique while I definitely gave flatlist unique IDs from my array, that warning is because of these rerendering. I guess I need to use Memo somewhere?:
const FeedScreen: React.FC<FeedScreenProps> = ({ navigation }) => {
const { postsSortedByDate } = checkPostsContext();
const navigateToCreatePostScreen = async () => {
navigation.navigate("CreatePostScreen");
};
const renderPost: ListRenderItem<IPost> = ({ item }) => (
<PostListItem post={item} key={item.uniquePostID} />
);
return (
<ScreenContainer>
<SafeAreaView>
<Header
containerStyle={styles.containerStyleHeader}
headerTitle="Factory X Feed"
rightIcon="add-photo-alternate"
onPressRightIcon={navigateToCreatePostScreen}
/>
</SafeAreaView>
{postsSortedByDate.length === 0 ? (
<View style={styles.noPostsContainer}>
<Text>
No posts exist at the moment, please create one by tapping on the
image icon in the header. In the meantime, as indicated by the
spinner below, we will continue to try to fetch posts in case some
do exist but are just loading slowly.
</Text>
<ActivityIndicator size="large" color="#0000ff" />
</View>
) : (
<FlatList
data={postsSortedByDate}
renderItem={renderPost}
keyExtractor={(item, index) => {
console.log("IDS:", item.uniquePostID.toString());
//HERE IS THE ISSUE as here I see same keys rendering //multiple times, and I checked and I did not save posts in a wrong way //for few of them to have the same key somehow. Its now about that. //Even logging posts themselves(here) shows me they re render.
return item.uniquePostID.toString();
}}
/>
)}
</ScreenContainer>
);
};
My PostListItem component:
const PostListItem: React.FC<PostItemProps> = ({ post }) => {
return (
<View style={styles.container}>
{post.postImage ? (
<Lightbox
renderContent={() => {
return (
<Image
source={{ uri: post.postImage }}
resizeMode="cover"
style={[
styles.imageInLightbox,
{
width: width,
height: height,
},
]}
/>
);
}}
>
<Image
source={{ uri: post.postImage }}
resizeMode="contain"
style={styles.image}
/>
</Lightbox>
) : null}
<React.Fragment>
{post.postDescription ? (
<Text style={styles.textStyle}>
{hashtagFormatter(post.postDescription)}
</Text>
) : (
<Text style={styles.textStyle}>{defaultPostDescription}</Text>
)}
</React.Fragment>
</View>
);
So why is PostListItem, I mean renderPost rerendering and how to fix it?

FlatList, Props ListHeaderComponent and Dynamic flatlist data

Sorry for the title, I wasn't inspired, and I didn't know what title to put for my problem.
I'm using Socket.io on my React native application to render some dynamics datas.
I'm using FlatList to display all this datas, and I need to show some contents like filters and buttons at the top of the FlatList.
So In my FlatList Props, I used ListHeaderComponent and in this component I display one other flatlist and contents.
My problem is :
When the main FlatList is updated with one of the Socket.io response (Every 5sec), all the components displayed by ListHeaderComponent aren't usable for a bit of time.
This period is like 500ms or less, but in the ListHeaderComponent I have some buttons / filters, and they are not clickable during this period.
And the problem is repeated each time the FlatList data is updated.
You can see my code (simplyfied) here :
export default function MyList({ navigation }) {
const [myData, setMyData] = useState<ResponseType[]>([])
// Socket Io
const socket = io(SOCKET_URL, { forceNew: true, timeout: 5000 })
useEffect(() => {
socket.on("connect", () => {
console.log("Connected to Socket ID : ", socket.id)
})
return () => {
socket.disconnect()
socket.close()
}
},[])
socket.on("socketResponse", (data: ResponseType) => {
setMyData(prevDate => [...prevDate, data])
})
const renderFilterItem = ({ item }: { item: Filter }) => <Text key={ item.id }>{ item.title }</Text>
const renderListItem = ({ item }: { item: ResponseType }) => <Text key={ item.id }>{ item.id }</Text>
const ListTop = () => (
<>
<TouchableOpacity onPress={() => navigation.navigate('HomePage')}>
<Text>BACK CONTENT</Text>
</TouchableOpacity>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={someFilters}
keyExtractor={(item) => item.id}
renderItem={renderFilterItem}
/>
</>
)
return (
<FlatList
ListHeaderComponent={ListTop}
data={myData}
keyExtractor={(item) => item.id}
renderItem={renderListItem}
maxToRenderPerBatch={5}
windowSize={2}
/>
)
}
I don't know how to explain better my problem. But If I move the component of ListHeaderComponent above the FlatList, there is no more problem.
But If I do this, just the FlatList is scrollable, and I want all the page scrollable.
I can't wrap my Flatlist with a ScrollView because of this error : VirtualizedLists should never be nested inside plain ScrollViews with the same orientation
Has anyone faced this problem ?
Thanks
try to use optimization for ListTop component as a possible problem here would be rendering of your functional component due to change of parent one.
here is the code sample.
import React, {useCallback} from 'react';
// your other codes
const ListTop = useCallback(() => (
<>
<TouchableOpacity onPress={() => navigation.navigate('HomePage')}>
<Text>BACK CONTENT</Text>
</TouchableOpacity>
<FlatList
horizontal
showsHorizontalScrollIndicator={false}
data={someFilters}
keyExtractor={(item) => item.id}
renderItem={renderFilterItem}
/>
</>
), [someFilters]);
here the component will be memoized until there is no change in someFilters.

How to architect a single modal component accessible by multiple components in React Native?

I'm new to react and I'm having trouble getting my head around the architecture of my app. Here's what I've got:
I use fetch() to get an array of records (records) from the backend. I use a couple of custom components to handle outputting the data. At the end of the tree is a series of touchable components which I need to open a single modal component on press. Here's a simple version of the code:
./app.js
<ScrollView>
{records.map((record, index) => {
return <Post key={index} post={record} />
})}
</ScrollView>
./src/components.js
function Post(props) {
return (
<Child info={props.post} />
...other custom components in here...
)
}
function Child(props) {
return (
<TouchableHighlight onPress={() => ...open modal...}>{props.info}</TouchableHighlight>
)
}
So I can't put my <Modal> in either of the components in components.js, as then I end up with one modal for each record in the database. So I figure I need to put it at the end of the <ScrollView> in app.js, but then how do I open and close it from the <Child> component? I've read that it's possible to pass info back up the chain to a parent component using a callback function, but I'm unsure of how to do that while also passing props down that contain the information of each record.
You can try this logic:
const [modalVisible, setModalVisible] = useState(false);
const handleVisibility = () => {
setModalVisible(!modalVisible)
}
return(
<View>
<YourWrappingModalComponent visible={modalVisible} />
<ScrollView>
{records.map((record, index) => {
return <Post
key={index}
post={record}
handleVisibility={handleVisibility} />
})}
</ScrollView>
</View>
)
To open modal from Child you do:
function Post(props) {
return (
<Child info={props.post} handleParentModal={props. handleVisibility} />
...other custom components in here...
)
}
function Child(props) {
return (
<TouchableHighlight onPress={() => props.handleParentModal()}>{props.info}</TouchableHighlight>
)
}
Using a component like YourWrappingModalComponent where you define your modal is very useful if you use your modal in many places.

Resources