Flatlist's children saying "Function components cannot be given refs." - reactjs

I'm getting an error "Warning: Function components cannot be given refs. Attempts to access this ref will fail" when I attempt to add refs to the children of a Flatlist. However, it only seems to happen when I add the refs to the UserinfoGridComponent and not the View component.
Basically, what I believe is happening, from a bunch of googling, is that the flatlist's children are considered functional components, just as the error says, and as such cant accept refs. (but I'm confused because the child has setState...)
What I really need is just a ref inside the child (UserinfoGridComponent) so that I can scroll the flatlist as needed, from within the UserinfoGridComponent component. Is there a way to get this to work?
Parent:
class HomeScreen extends React.Component {
displayType = ({ item, index }) => {
const isBookmarked = (this.state.bookmarks.map(bookmark => bookmark.id).indexOf(item.id) !== -1)
return (
<View
ref={test => console.log("test ref 2", test)}
key={item.id}
style={[styles.shadowContainer, style={marginHorizontal: 15, marginBottom:25}]}>
<UserinfoGridComponent
ref={test => console.log("test ref 1", test)}
checked={ isBookmarked }
images={item.promoImages}
firstImg={item.promoImages ? (item.promoImages[0] || null) : null}
secondImg={item.promoImages ? (item.promoImages[1] || null) : null}
avatar={item.avatar || ''}
name={item.name || ''}
mainSkill={User.extractSkill(item.skill)}
secondarySkill={( item.otherSkills && item.otherSkills.length > 0 ) ? User.extractSkill(item.otherSkills[0]) : ''}
city={item.location.locality}
state={item.location.region}
// key={index}
index={index}
user={item}
onUserInfoPress={() => {
this.props.navigation.navigate('Profile', {
user: item,
clearAllFilters: this.clearAllFilters });
}}
onBookmarkPress={this.onBookmarkPress}
/>
</View>
)
}
render() {
const { creatorSearch } = this.props.data
return (
<View style={{flex:1, backgroundColor: 'white'}}>
<Animated.View style={{ opacity: this.state.initialOpacityAnimation, flex: 1}}>
<FlatList
ref={(list) => { this.listRef = list }}
contentInset={{top: 0, left: 0, bottom: 150, right: 0}}
scrollIndicatorInsets={{top: 60, left: 0, bottom: 70, right: 0}}
data={creatorSearch && this.state.initialContentLoaded ? creatorSearch.items : []}
onEndReachedThreshold={0.3}
onEndReached={() => this.scrollEnd()}
ListHeaderComponent={this.listHeaderText}
ListEmptyComponent={this.emptyListView}
extraData={this.state.bookmarks.length}
renderItem={this.displayType}
scrollEventThrottle={1}
/>
</Animated.View>
</View>
)
}
}
Child:
class UserInfoGridComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
loadedUser: { media: { items: [] }},
promoImages: this.props.user.promoImages
}
}
render() {
const combinedImages = this.state.loadedUser.media.items.length > 0 ? this.state.loadedUser.media.items : this.state.promoImages
return (
<View>
<FlatList
ref={ref => console.log("THIS IS A TEST", test)}
horizontal={true}
data={combinedImages}
renderItem={this.imageDisplay}
style={{padding:imageMargin*2}}
showsHorizontalScrollIndicator={false}
/>
</View>
}

reference should be assigned as ref={(ref) => this._flatlist = ref}
and you can access flatlist by using this._flatlist

Related

Flatlist Does Not Appear - Nested Flatlists

I am attempting to nest a Flatlist. I am using two Realm object arrays and need to conditionally display items from the "ingredients" array based on a value within the "inventories" array.
I am wondering if I have my "return statements" placed incorrectly or whether my logic is skewed. Please advise. Any help would be much appreciated. Thank you.
import * as React from 'react';
import {View, Text, FlatList} from "react-native";
import realm from '../schemas/InventoryDatabase';
export default class ViewInventory extends React.Component {
constructor(props) {
super(props);
this.state = {
FlatListInventoryItems: [],
};
this.state = {
FlatListIngredientItems: [],
};
var inventories = Object.values(realm.objects('Inventories'));
var ingredients = Object.values(realm.objects('Ingredients'));
this.state = {
FlatListInventoryItems: inventories,
};
this.state = {
FlatListIngredientItems: ingredients,
};
}
ListViewItemSeparator = () => {
return (
<View style={{ height: 0.5, width: '100%', backgroundColor: '#000' }} />
);
};
render() {
return (
<View>
<FlatList
data={this.state.FlatListInventoryItems}
ItemSeparatorComponent={this.ListViewItemSeparator}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<View style={{ backgroundColor: 'white', padding: 20 }}>
<Text>Inventory ID: {item.recordID}</Text>
<Text>Name: {item.inventoryName}</Text>
<Text>Date: {item.date}</Text>
<FlatList
data={this.state.FlatListIngredientItems}
ItemSeparatorComponent={this.ListViewItemSeparator}
keyExtractor={(item2, index) => index.toString()}
renderItem={({ item2 }) => {
if (item2.inventoryID == item.recordID) {
return (
<View style={{ backgroundColor: 'gray', padding: 20 }}>
<Text>Ingredient ID: {item2.ingredientID}</Text>
<Text>Ingredient Type: {item2.ingredientType}</Text>
<Text>Ingredient: {item2.ingredient}</Text>
</View>
);
}
}}
/>
</View>
)}
/>
</View>
);
}
}
Everything looks ok.
However, ScrollViews should never be nested. Consider using map instead of your second FlatList.

Show View when scroll up Scrollview

How to limit the quantity of View inside of a scrollview.
My component take too much time to render, because the map function renders too many views. I need to show only 10 views, and when scroll up, renders more 10.
I'm using react native, hooks and typescript.
First of all, if you have a large number of list data don't use scrollview. Because initially, it loads all the data to scrollview component & it costs performance as well.
Use flatlist in react-native instead of scrollview & you can limit the number of items to render in the initially using initialNumToRender. When you reach the end of the scroll position you can call onEndReached method to load more data.
A sample will like this
import React, { Component } from "react";
import { View, Text, FlatList, ActivityIndicator } from "react-native";
import { List, ListItem, SearchBar } from "react-native-elements";
class FlatListDemo extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
this.setState({ loading: true });
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
};
handleRefresh = () => {
this.setState(
{
page: 1,
seed: this.state.seed + 1,
refreshing: true
},
() => {
this.makeRemoteRequest();
}
);
};
handleLoadMore = () => {
this.setState(
{
page: this.state.page + 1
},
() => {
this.makeRemoteRequest();
}
);
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderHeader = () => {
return <SearchBar placeholder="Type Here..." lightTheme round />;
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
render() {
return (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
keyExtractor={item => item.email}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
onRefresh={this.handleRefresh}
refreshing={this.state.refreshing}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
</List>
);
}
}
export default FlatListDemo;
Check this for more informations.
I changed to Flatlist! But initialNumToRender is not working as expected.
The flatlist is rendering all 150 transactions, not only 15, and i have no idea what to do.
I'm running .map() from another array with all others transactions to create newMonths with only those transactions that i want to use data={newMonths}.
let newMonths = [];
const createArrayMonth = histInfos.map(function (info) {
if (info.created_at.slice(5, 7) === month) {
newMonths = [info].concat(newMonths);
}
});
them, i created my component:
function Item({ value }: { value: any }) {
let createdDay = value.item.created_at.slice(8, 10);
let createdMonth = value.item.created_at.slice(5, 7);
let createdYear = value.item.created_at.slice(2, 4);
function dateSelected() {
if (
month === createdMonth &&
year === createdYear &&
(day === '00' || day == createdDay)
) {
console.log('foi dateSelected');
const [on, setOn] = useState(false);
const Details = (on: any) => {
if (on === true) {
return (
<View style={styles.infos}>
<Text style={styles.TextInfos}>
{' '}
CPF/CNPJ: {value.item.cpf_cnpj}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Criado em: {value.item.created_at}{' '}
</Text>
<Text style={styles.TextInfos}>
{' '}
Endereço da carteira: {value.item.address}{' '}
</Text>
<Text style={styles.TextInfos}> Valor: {value.item.amount}BTC </Text>
</View>
);
} else {
return <View />;
}
};
return (
<View>
<TouchableOpacity
style={styles.card}
onPress={() => setOn(oldState => !oldState)}>
<View style={styles.dateStyleView}>
<Text style={styles.dateStyle}>{createdDay}</Text>
</View>
<View style={styles.left}>
<Text style={styles.title}>Venda rápida</Text>
<Text style={styles.semiTitle}>
{
{
0: 'Pendente',
1: 'Aguardando conclusão',
2: 'Cancelado',
100: 'Completo',
}[value.item.status]
}
</Text>
</View>
<View style={styles.right2}>
<Text style={styles.price}>R$ {value.item.requested_value}</Text>
</View>
</TouchableOpacity>
<View>{Details(on)}</View>
</View>
);
}
}
return dateSelected();}
and i call it here
return (
<ScrollView>
...
<View style={styles.center}>
...
<View style={styles.middle2}>
...
<FlatList
extraData={[refresh, newMonths]}
data={newMonths}
renderItem={(item: any) => <Item value={item} />}
keyExtractor={(item, index) => index.toString()}
initialNumToRender={15}
/>
</View>
</View>
</ScrollView>);}
The scroll bar in the right, start to increase until renders all transactions from the data:
App scroll bar

react native socket response not updating in view

I have connect socket successfully and getting proper response also from socket connection. State is updating successfully but issue is data is not updating in screen (renderTabSection not render updated data).
class Test extends Component{
constructor (props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
componentDidMount(){
const { restaurantId } = this.props;
this.socket = new WebSocket('wss://test.com/' + id);
this.socket.onopen = (data) =>{
console.log('Socket connected');
}
this.socket.onmessage = (e) => {
this.props.socketData(e.data);
//this.socketUpdate();
};
this.socket.onclose = () => {
this.socket = new WebSocket('wss://test.com/' + id);
}
}
componentWillMount(){
this.props.resetVenueDetails();
this.props.showLoader();
}
componentWillReceiveProps(nextProps){
this.renderTabSection();
}
componentWillUnmount() {
this.socket.close();
}
onTabClick(tabId){
this.props.onTabChange(tabId);
}
renderTabSection(){
let playlist = this.props.playlists;
const {
container,
tabHeaderStyle,
tabHeadStyle,
tabDetailBox,
albumTextStyle,
selectedTabStyle,
hideTabContentStyle
} = styles;
return (
<View style={{flex:1, borderBottomColor: '#000', borderBottomWidth: 2 }}>
<View style={{flex:0,flexDirection:'row'}}>
<View style={tabHeadStyle}>
<TouchableOpacity onPress={this.onTabClick.bind(this,1)}>
<Text style={ [tabHeaderStyle, this.props.tabSelected == 1 ? selectedTabStyle : '' ] }>
Playlist
</Text>
</TouchableOpacity>
</View>
</View>
<View style={[container, this.props.tabSelected == 1 ? "" : hideTabContentStyle]} >
<ScrollView horizontal={true}>
<View style={{ flex: 1,flexDirection: 'row' }}>
{ this.renderPlaylistAlbums('user', playlist) }
</View>
</ScrollView>
</View>
</View>
);
}
renderPlaylistAlbums(type, playlist){
let songsArray = [];
const { tabDetailBox, albumTextStyle } = styles;
playlist = playlist.length > 0 ? playlist : PLAYLIST_SECTION;
playlist.filter( (data, i) => {
if( data.type == type ){
if(data.songs.length > 0 ){
let image = data.songs[0] && data.songs[data.songs.length -1].imageUrl ?
{ uri : data.songs[data.songs.length -1].imageUrl } :
require('../assets/images/playlist.png');
let imageStyle = data.songs[0] && data.songs[data.songs.length -1].imageUrl ?
{ width: '70%',height:'60%', borderRadius: 10 } :
{ width: '60%',height:'60%' };
songsArray.push(
<TouchableOpacity key={i}>
<View style={ tabDetailBox }>
<Image
source={ image }
style={ imageStyle } />
<Text style={ albumTextStyle }>{ data.name }</Text>
</View>
</TouchableOpacity>
);
}
}
});
return songsArray;
}
loadSpinner(){
if( this.props.loading ){
return ( <View style={{ flex:1, position:'absolute', width:'100%' }} ><Spinner /></View> );
}
}
render(){
const { backgroundImage } = styles;
return (
<View style={{ flex: 1 }}>
<ImageBackground source={APP_BACKGROUND_IMAGE.source} style={backgroundImage}>
{ this.loadSpinner() }
<View style={{ flex:1, backgroundColor:'rgba(0,0,0,.7)' }}>
{ this.renderTabSection() }
</View>
</ImageBackground>
<Footer headerText='Home' />
</View>
);
}
}
const mapStateToProps = state => {
return {
error:state.home.error,
loading:state.home.loading,
tabSelected:state.restaurantDetail.tabSelected,
playlists: state.restaurantDetail.playlists
}
}
export default connect(mapStateToProps, { onTabChange, socketData, showLoader, resetVenueDetails })(Test)
I have connect socket successfully and getting proper response also from socket connection. State is updating successfully but issue is data is not updating in screen (renderTabSection not render updated data).
I think you are not setting any state because setState will cause the component to re-render, this is how you can setState
constructor(props){
this.state = {
dataUpdated: false,
anyUseFullVariable: '',
}
}
you can setState when you get response from sockets like this
this.setState({dataUpdated: true})
then you can use the state within render component like this
this.state.dataUpdated
this will re-render the component. This is just an example try doing something similar.
Update
You can also use componentWillRecieveProps, this callback function is triggered whenever there is some change in props
componentWillRecieveProps(nextProps){
if(!isFetching && this.props.data !== nextProps.data) // or implement your preferred condition
this.setState({UpdateList: this.props.data}) // this would call the render function
}
}

General solution for subscribing to listeners in react native

Is there a reusable way of subscribing to listener like keyboard events.
Actually I have a button with position absolute at the very bottom of my screen and when keyboard pops up it comes floating on top and that does not look very good.
So I am hiding that button when keyboard is visible but if you have similar scenario on multiple screens it becomes headache to add subscription on every screen currently I am doing it this way.
class Profile extends Component {
constructor(props) {
super(props);
this._keyboardDidShow = this._keyboardDidShow.bind(this);
this._keyboardDidHide = this._keyboardDidHide.bind(this);
}
componentDidMount() {
// subscribing to keyboard listeners on didMount
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
this._keyboardDidShow
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
this._keyboardDidHide
);
}
_keyboardDidShow() {
this.setState({
keyboardVisible: true,
});
}
_keyboardDidHide() {
this.setState({
keyboardVisible: false,
});
}
componentWillUnmount() {
// unsubscribing listeners on unMount
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
const AnimatedBottomButton = Animated.createAnimatedComponent(BottomButton);
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
{!this.state.keyboardVisible && (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
)}
</ScrollView>
);
}
}
I don't like the above solution since I have to add subscription related code to every Component I want to subscribe for keyboard events I am new to javascript and still learning it.
If any one out there can help me with some general solution of it would be very good.
Custom components come in handy in these situations. You can create a single component with desired behaviors implemented and then you can add that component to the screens you want to use.
Sample
export default class CustomButton extends Component {
state = {
visible: true
}
componentDidMount() {
// subscribing to keyboard listeners on didMount
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => this._toggleVisiblity(false)
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => this._toggleVisiblity(true)
);
}
_toggleVisiblity = (visible) => {
this.setState({ visible })
}
componentWillUnmount() {
// unsubscribing listeners on unMount
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
if (this.state.visible === false) return null
return (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
);
}
}
class Profile extends Component {
render() {
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
<CustomButton />
</ScrollView>
);
}
}
You can go a bit further if you like and create a HOC.
Sample
const withKeyboardEvents = WrappedComponent => {
return class extends Component {
state = {
visible: true,
};
componentDidMount() {
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => this._toggleVisiblity(false)
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => this._toggleVisiblity(true)
);
}
_toggleVisiblity = visible => {
this.setState({ visible });
};
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
return (
<React.Fragment>
{this.state.visible === true && (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
)}
<WrappedComponent />
</React.Fragment>
);
}
};
};
class Profile extends Component {
render() {
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
</ScrollView>
);
}
}
export default withKeyboardEvents(Profile)

Change background of list item

How can I change the background color of multiple list items when they are selected? I am using react-native-accordian and react-native-collapsible and using flat list within content.
_renderContent(section, i, isActive)
{
//console.log("MY DATA---",section.time_slots);
return (
<List
style={inStyles.body}
containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={section.time_slots}
renderItem={
({ item,index }) =>
(
<ListItem
onPress={() => this.selectSlot(item,section.date,index)}
style = {[inStyles.list , {marginLeft : 15}, {marginRight : 5},
{backgroundColor: (this.state.selectedItem[index]) ? 'green' : 'red'}]}
title={`${item}`}
containerStyle={{ borderBottomWidth: 0 }}
/>
)
}
keyExtractor={item => section.date+item}
ItemSeparatorComponent={this.renderSeparator}
ListFooterComponent={this.renderFooter}
/>
</List>
);
}
I just want to change the style of a list item dynamically using TouchableOpacity. But unable to do so.
You should change your code as below, your class would be like this:
contructor (props) {
super(props)
let selectedItemTemp = []
for(let i=0; i<section.time_slots.length; i++) { //section.time_slots is your FlatList data
selectedItemTemp.push(false)
}
this.state = {selectedItem: selectedItemTemp}
}
selectSlot = (item, section.date, index) => {
let {selectedItem} = this.state
selectedItem[index] = !selectedItem[index]
this.setState({selectedItem})
... // your other codes
}
render() {
return (
...
<FlatList
data={section.time_slots}
renderItem={
({ item, index }) => (
<TouchableOpacity
onPress={() => this.selectSlot(item,section.date, index)}>
<ListItem style = {[inStyles.list , {marginLeft : 15}, {marginRight : 5}, {backgroundColor: (this.state.selectedItem[index]) ? 'green' : 'white'}]}
title={`${item}`}
containerStyle={{ borderBottomWidth: 0 }}
/>
</TouchableOpacity>
)
} {item => section.date+item}
ItemSeparatorComponeultiple Selectiont={this.renderSeparator}
ListFooterComponent={this.renderFooter}
/>
...
);
}

Resources