Check visibility of item at the screen - reactjs

I have FlastList with data. I need to check if item from the Flastlist is seen. So if i do not see data at my screen i do not have to do anything, but if i see i have to console.log data info. And when i am scrolling i have to console.log data that is visibile. I am trying to use onViewableItemsChanged with viewabilityConfig, but when i console.log data, it returns all data from FlastList but not data that is seen. Help me please.
I will be very thankfull!
_onViewableItemsChanged = ({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems.map(item => item.item.text));
};
_viewabilityConfig = {
viewAreaCoveragePercentThreshold: 100
};
//....
<FlatList
data={this.state.postData}
initialNumToRender={0}
ItemSeparatorComponent = {this.FlatListItemSeparator}
renderItem={({item}) => {
return (
<View style={{paddingTop: 15}}
ref={ (divElement) => { this.divElement = divElement } }
>
// data
)}
}
onViewableItemsChanged={this._onViewableItemsChanged}
viewabilityConfig={this._viewabilityConfig}
keyExtractor={item => item.id}
/>

In your comment you specified that the FlatList is inside a view and scrollview. I tried to reproduce it this way:
<View style={styles.container}>
<ScrollView style={styles.scrollView}>
<FlatList
data={DATA}
initialNumToRender={0}
renderItem={renderItem}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
keyExtractor={item => item.id}
/>
</ScrollView>
</View>
And indeed, console.log shows all data in this case. When I removed ScrollView so that the FlatList is inside the View element (with flex:1) then it works correctly - console.log shows only visible elements. My code:
const DATA = [
{
id: '1',
title: 'First Item',
},
// ... more elements
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const renderItem = ({item}) => {
return (
<View style={{paddingTop: 15}}
ref={ (divElement) => { this.divElement = divElement } }
>
<Item title={item.title} />
</View>
)
}
const viewabilityConfig = {
viewAreaCoveragePercentThreshold: 100
};
const onViewableItemsChanged = ({ viewableItems, changed }) => {
console.log("Visible items are", viewableItems.map(item => item.item.title));
};
export default function App() {
return (
<View style={styles.container}>
<FlatList
data={DATA}
initialNumToRender={0}
renderItem={renderItem}
onViewableItemsChanged={onViewableItemsChanged}
viewabilityConfig={viewabilityConfig}
keyExtractor={item => item.id}
/>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
title: {
fontSize: 32,
},
});

Related

Mobx store do not update with observer

I have a simple react native app with two screens.
First screen is the list, where you see your selected groups, and you can remove them, by clicking on trash icon:
export const Settings: NavioScreen = observer(({ }) => {
...
return (
<View flex bg-bgColor padding-20>
<FlashList
contentInsetAdjustmentBehavior="always"
data={toJS(ui.savedGroups)}
renderItem={({item}) => <ListItem item={item} />}
estimatedItemSize={20}
/>
</View>
);
});
};
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<TouchableOpacity onPress={() => ui.deleteGroup(item)}>
<Icon name={'trash'}/>
</TouchableOpacity>
</View>
);
};
The second screen is also the list, where you can add and remove the subjects from the list:
export const Playground: NavioScreen = observer(() => {
...
const groupsToShow =
ui.search && ui.search.length > 0
? ui.groups.filter((p) =>
p.name.toLowerCase().includes(ui.search.toLowerCase())
)
: ui.groups;
return (
<View >
<FlashList
data={toJS(groupsToShow)}
renderItem={({item}) => <ListItem item={item} />}
/>
</View>
);
});
const ListItem = ({item}: any) => {
const { ui } = useStores();
return (
<View>
<Text textColor style={{ fontWeight: 'bold', fontSize: 15 }}>{item.name}</Text>
<View>
<If
_={ui.isGroupSaved(item)}
_then={
<TouchableOpacity onPress={(e) => {ui.deleteGroup(item)}}>
<AntDesign name="heart" size={20} color={Colors.primary} />
</TouchableOpacity>
}
_else={
<TouchableOpacity onPress={(e) => {ui.addGroup(item)}}>
<AntDesign name="hearto" size={20} color={Colors.primary} />
</TouchableOpacity>
}
/>
</View>
</View>
);
};
And now when I remove the group from the first list, the heart icon do not update on the second list. But it should, because there is an if statement on second list, that checks if the group is saved. And if it is not, the heart should have the name="hearto"
I have tried to use the state instead mobx library but it does not also help.
Here is my store written with mobx:
export class UIStore implements IStore {
savedGroups = [];
constructor() {
makeAutoObservable(this);
makePersistable(this, {
name: UIStore.name,
properties: ['savedGroups'],
});
}
addGroup = (group: any) => {
if (true === this.isGroupSaved(group)) {
return;
}
this.savedGroups.push(group);
}
isGroupSaved = (group: any) => {
return -1 !== this.savedGroups.findIndex(g => g.id === group.id);
}
deleteGroup = (groupToDelete: any) => {
this.savedGroups = this.savedGroups.filter((group) => group.id !== groupToDelete.id);
}
}

Invariant Violation: Text strings must be rendered within a <Text> component while using flatList

I am using flat list to display data which is coming from unsplash api. But here it keeps on complaining to saying this
Invariant Violation: Text strings must be rendered within a component
I am not even using any text component. I have no idea what is wrong here.
App.js
export default function App() {
const [loading, setLoading] = useState(true);
const [image, setImage] = useState([]);
const {height, width} = Dimensions.get('window');
const URL = `https://api.unsplash.com/photos/random?count=30&client_id=${ACCESS_KEY}`;
useEffect(() => {
loadWallpapers();
}, [])
const loadWallpapers =() => {
axios.get(URL)
.then((res) => {
setImage(res.data);
setLoading(false);
}).catch((err) => {
console.log(err)
}).finally(() => {
console.log('request completed')
})
}
const renderItem = (image) => {
console.log('renderItem', image);
return (
<View style={{height, width}}>
<Image
style={{flex: 1, height: null, width: null}}
source={{uri : image.urls.regular}}/>
</View>
)
}
return loading ? (
<View style={{flex: 1, backgroundColor: 'black', justifyContent: 'center',alignItems: 'center'}}>
<ActivityIndicator size={'large'} color="grey"/>
</View>
): (
<SafeAreaView style={{flex: 1, backgroundColor: 'black'}}>
<FlatList
horizontal
pagingEnabled
data={image}
renderItem={({ item }) => renderItem(item)} />}
/>
</SafeAreaView>
)
}
I thing data of Flatlist is null, try
<FlatList
horizontal
pagingEnabled
data = {image ? image : []}
renderItem={({ item }) => renderItem(item)} />}
/>
I needed to do something like this to make it work.
const renderItem = ({ item }) => { <---- I have destructured item here
console.log(item)
return (
<View style={{ flex: 1 }}>
</View>
);
};
<FlatList
scrollEnabled={!focused}
horizontal
pagingEnabled
data={image}
renderItem={renderItem}
/>

TouchableOpacity onPress() function doesn't works

I tried to create a DraggableList with some checkboxes, the problem is my component doesn't trigger the "onPress" while the "onLongPress" method working fine...
I tried to remove the "onLongPress" method, but still the same.
Here is my whole component :
class Menus extends React.Component {
state = {
menusItems: global.conf.Menus.map((d, index) => ({
id: d.id,
key: `item-${d.id}`,
label: d.label,
path: d.path,
value: d.value,
backgroundColor: `rgb(${Math.floor(Math.random() * 255)}, ${index *
5}, ${132})`,
})),
};
renderItem = ({item, index, drag, isActive}) => {
return (
<TouchableOpacity
style={{
backgroundColor: isActive ? 'blue' : item.backgroundColor,
alignItems: 'center',
justifyContent: 'center',
}}
onLongPress={drag}
onPress={() => console.log('puff')}>
<Checkbox
status={item.value ? 'checked' : 'unchecked'}
key={item.id}
title={item.label}
/>
</TouchableOpacity>
);
};
return (
<View style={{flex: 1}}>
<DraggableFlatList
data={this.state.menusItems}
renderItem={this.renderItem}
keyExtractor={(item, index) => `draggable-item-${item.key}`}
onDragEnd={({data}) => this.setMenus(data)}
/>
</View>
);
}
}
export default Menus;
I don't know what is wrong ....

How to stop React-Native FlatList Interfering with other components

I have a simple React-Native app that uses FlatList with Redux. The problem is that when the list becomes long and reaches the bottom of the screen where the input elements exists it disrupts these input elements even though they are in another component and container. I've tried a million fixes for this, but nothing seems to work.
How can I do something like only have FlatList occupy 2/3rds of the screen?
This is a screenshot of the issue (when the items reach the input boxes it results in the input boxes shrinking and being disrupted):
This is the app file that contains all my components:
export default class App extends Component {
render() {
return (
<Provider store={createStore(reducers)}>
<View style={{ flex: 1 }}>
<ItemsList />
<AddItem />
</View>
</Provider>
);
}
}
This is the component that uses FlatList:
class ItemsList extends Component {
render() {
return (
<List>
<FlatList
data={this.props.items}
renderItem={({ item }) => (
<ListItem
name={item.item} id={item.id}
/>
)}
keyExtractor={item => item.id.toString() }
/>
</List>
);
}
}
const mapStateToProps = state => {
return { items: state.items };
};
export default connect(mapStateToProps)(ItemsList);
The code for addItem is:
class AddItem extends Component {
state = {
item: "",
quantity: ""
}
onButtonPress() {
this.props.addItem(this.state)
this.setState({
item: "",
quantity: 0
})
}
render() {
const { input, container, add, addText } = styles;
return (
<View style={container}>
<TextInput placeholder="add item"
placeholderTextColor="rgba(0, 0, 0, 0.5)"
style={input}
onChangeText={item => this.setState({ item })}
value={this.state.item}
/>
<TextInput placeholder="add item"
placeholderTextColor="rgba(0, 0, 0, 0.5)"
style={input}
onChangeText={quantity => this.setState({ quantity })}
/>
<TouchableOpacity style={add} onPress={this.onButtonPress.bind(this)}>
<Text style={addText}>Add Item</Text>
</TouchableOpacity>
</View>
);
}
}
export default connect(null, {addItem})(AddItem);
const styles = {
input: {
backgroundColor: 'rgb(208, 240, 238)',
paddingVertical: 15,
paddingHorizontal: 10,
marginBottom: 5
},
add: {
backgroundColor: 'black',
paddingVertical: 15,
},
addText: {
textAlign: 'center',
color: 'white'
},
container: {
padding: 20,
flex: 1,
justifyContent: 'flex-end'
}
};
First of all, remove <List> from your ItemsList since you already use FlatList. Then, for your FlatList to take up 2/3 of the screen height do this:
class ItemsList extends Component {
render() {
return (
<View style={{ flex: 2 }}>
<FlatList
data={this.props.items}
renderItem={({ item }) => (
<ListItem
name={item.item} id={item.id}
/>
)}
keyExtractor={item => item.id.toString() }
/>
</View>
);
}
}
const mapStateToProps = state => {
return { items: state.items };
};
export default connect(mapStateToProps)(ItemsList);

unexpected token when trying to declare a variable inside flatlist in react native

i'm trying to declare an variable inside the FlatList component in React Native
But i get unexpected token, when i do declare it.
const FixturesScreen = ({ navigation }) => (
<ScrollView style={styles.container}>
<FlatList
data={clubData.data.clubs}
renderItem={({ item }) => (
let fixture = item.name //unexpected token
<View>
<View>
<Text style={styles.itemTitle}>{item.name}</Text>
<ScrollView horizontal>
<Text style={styles.listItem}>{fixture}</Text>
</ScrollView>
</View>
</View>
)
}
/>
</ScrollView>
)
here is my full FixturesScreen cdoe
import React from 'react'
import { View, Text, FlatList, ScrollView, StyleSheet } from 'react-native'
import Ionicons from 'react-native-vector-icons/Ionicons'
import clubData from '../../clubData'
const styles = StyleSheet.create({
container: {
backgroundColor: '#4BABF4',
},
itemTitle: {
color: 'black',
fontSize: 20,
marginTop: 20,
marginBottom: 10,
marginLeft: 15,
},
listItem: {
color: '#FFFFFF',
fontSize: 18,
textAlign: 'center',
marginRight: 15,
marginLeft: 15,
backgroundColor: '#77BEF5',
width: 120,
paddingVertical: 10,
},
})
const CURRENTTGAMEWEEK = 30
const i = CURRENTTGAMEWEEK
const nxt1 = i + 1
const nxt2 = nxt1 + 2
const nxt3 = nxt2 + 1
const nxt4 = nxt3 + 1
const nxt5 = nxt4 + 1
// let fixture
// const team = clubData.data.clubs[0].name
// const hTeam = clubData.data.clubs[0].fixtures[0].homeTeam
// const hTeamShort = clubData.data.clubs[0].fixtures[0].homeTeamShort
// const aTeamShort = clubData.data.clubs[0].fixtures[0].awayTeamShort
//
// if (team === hTeam) // working
// fixture = aTeamShort
// else
// fixture = hTeamShort
console.log(`Now is playing ${fixture}`)
const FixturesScreen = ({ navigation }) => (
<ScrollView style={styles.container}>
<FlatList
data={clubData.data.clubs}
renderItem={({ item }) => (
let fixture = item.name
<View>
<View>
<Text style={styles.itemTitle}>{item.name}</Text>
<ScrollView horizontal>
<Text style={styles.listItem}>{fixture}</Text>
</ScrollView>
</View>
</View>
)
}
/>
</ScrollView>
)
FixturesScreen.navigationOptions = {
tabBarTestIDProps: {
testID: 'TEST_ID_HOME',
accessibilityLabel: 'TEST_ID_HOME_ACLBL',
},
tabBarLabel: 'Main',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-home' : 'ios-home-outline'}
size={26}
style={{ color: tintColor }}
/>
),
}
export default FixturesScreen
So basically what i'm trying to do is declare homeTeam, awayTeam and Fixture inside the flatlist, so i can do an if/else conditional rendering inside the flatlist. I can achieve that outside the flatlist component but it is not right, because i can not compare all objects at once.
While using arrow functions () => ('someValue') is a shortcut for () => { return 'someValue'}.
(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
// equivalent to: (param1, param2, …, paramN) => { return expression; }
// Parentheses are optional when there's only one parameter name:
(singleParam) => { statements }
singleParam => { statements }
// A function with no parameters should be written with a pair of parentheses.
() => { statements }
// Parenthesize the body of function to return an object literal expression:
params => ({foo: bar})
So if you want to run some code before returning a value you should do like below;
const FixturesScreen = ({ navigation }) => (
<ScrollView style={styles.container}>
<FlatList
data={clubData.data.clubs}
renderItem={({ item }) => { //change to curly braces
let fixture = item.name;
// do something here
return (
<View>
<View>
<Text style={styles.itemTitle}>{item.name}</Text>
<ScrollView horizontal>
<Text style={styles.listItem}>{fixture}</Text>
</ScrollView>
</View>
</View>
);
}}
/>
</ScrollView>
)

Resources