How to get scrollY point in React Native - react-native

I'm building a header and I want to hide the header content UNTIL the user scrolls down to the certain point.
(My Approach)
Get scrollY position (How much did user scroll vertically from top?)
if scrollY is greater than 50
show the header name.
Here is render() method. I manually built header. I'm getting scrollY value and set it to this.state.scrollY
<View style={{flex:1}}>
<View style={styles.header}>
{this._renderHeader(profile.username)}
</View>
<ScrollView style={styles.root}
onScroll={(event)=>{this.setState({scrollY: event.nativeEvent.contentOffset.y})}}>
// setting this.state.scrollY for scroll Y position
...
Here is _renderHeader function. (rendering header)
_renderHeader = (username) => {
return (
<View style={styles.headerLayout}>
<View rkCardHeader style={styles.left}>
...
<View style={{justifyContent: 'center'}}>
{this._renderUsername(username)}
// I want to show this string ONLY IF user
// scrolls down the window to the certain point.
</View>
....
</View>
);
}
(Here is the problem) This doesn't render only or it doesn't trigger _renderUsername.
_renderUsername = (username) => {
let show = (this.state.scrollY > 50) ? true : false;
if(show) {
return (
<RkText rkType='header3'>{username}</RkText>
);
} else {
return <View />
}
}

You can keep a track of scrollY in redux store and map it to the header component.
While rendering the header component use ternary expression to decide whether to render or not.
For example -
(From the store you will get the value this.props.scrollY)
render() {
return (
{this.props.scrollY > 50 ? <AppHeader /> : ""}
);
}

Related

How do i display card with different items when key pass through navigator in react native

When pass the item to the next screen through navigator of radio.js the other screen attendence.js card shows all item details with same id in the card with multiple times when an C# API is called in attendancjs.
I want to show different items in the card when calling an API in attendance.js.
Radio.Js
onRadioBtnClick = () => {
data.map((item) =>
{
if(item.attendanceType==checked)
{
// if (index < item) {
// console.log(item)
// return item;
// }
// for(i=0;i<item.length;i++)
// {
// elements=item[i]
console.log({item})
// }
navigation.navigate('attendance',{item})
} }
);
enter image description here
Attendance.js
return (
<ScrollView>
{
// {`${post.attendanceTime}`+ `${post.attendanceType}`}
// console.log(item)
data.map(() => (
<View style={styles.view} >
<Card style={styles.cardWrapper} >
<View style={styles.card}>
<Text style={styles.title}>Key:{item.id} {'\n'}
Latitude:{item.latitude} {'\n'} Longitude:{item.longitude}{'\n'}
Attentance Time:{item.attendanceTime} {'\n'}
Attentance:{`${item.attendanceType}`}{item.attendanceType=="i"? (<Image style={styles.icon} source={require('../assests/checked.png')} /> ):(<Image style={styles.icon} source={require('../assests/delete.png')} />)}
{'\n'}
Employee Name: {item.userInfo.displayName}
</Text>
</View>
</Card>
</View>
))
You are passing object navigation.navigate('attendance',{item}) from Radio.js and in Attendance.js you are iterate over data array and display only item(which is one object) to length of data. So it will take length of data and display one item which is passed from Radio.js

Dynamically populating nested react-native-collapsible

I need to populate a menu with items from an api request.
I made some sample items; const items. Some of the menu items have children, and the children can also have children, so I need to nest several levels of menu items.
I made an Accordion() with Collapsible from react-native-collapsible and an AccordionItem() for items that have no children.
function App() renders the menu items twice. Once by manually adding Accordions and AccordionItems and once by mapping items[]. RootMenuItem() is called for each top level item and renders that item and its sub-items by recursion.
When manually adding each item it works the way I want it to. I need to populate the menu programatically, but nested accordions rendered by RootMenuItem() are misbehaving on android and iOS. When testing in Web on snack.io it seems to be working fine.
Here is a snack with my complete App.js:
https://snack.expo.dev/#dissar/nested-collapsibles
Am I doing something wrong?
Does anybody have any tips for doing this in a better way?
PS: The dynamically rendered items have weird widths when testing on snack.io, but don't worry about that.
I seem to have fixed it myself by removing the View on line 46 and 56;
function RootMenuItem({item}){
if(item.children.length > 0) {
return(
<View style={{flex: 1}} key={item.id}> // <---- I removed this
<Accordion item={item} style={styles.menuItemView}>
{
item.children.map(child => (
<View style={{paddingLeft: 18}} key={child.id}>
<RootMenuItem item={child} style={{paddingLeft: 10}}/>
</View>
))
}
</Accordion>
</View> // <---- Also removed this
)
}
else return (
<AccordionItem item={item}/>
)
}
Not really sure though why that View made the nested accordions not work as they should. Please let me know if you have the answer.
I have a better solution without using any 3rd party library. This is completely customised and easy to understand. I used the same format of data as you used.
first of all, we have a component
const [active, setActive] = useState(null);
return (
<ScrollView
style={{ marginTop: 50 }}
contentContainerStyle={styles.container}>
{arr.map((x, i) => (
<Item
key={x.name}
active={active}
i={i}
setActive={setActive}
child={x.child}
/>
))}
</ScrollView>
);
then for the list items and their child
function Item({ i, active, setActive, child }) {
const onPress = () => {
LayoutAnimation.easeInEaseOut();
setActive(i == active ? null : I);
};
const [childActive, setChildActive] = useState(null);
const open = active == I;
return (
<TouchableOpacity style={styles.item} onPress={onPress} activeOpacity={1}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text>Header - {i + 1}</Text>
{child.length ? <Text>{open ? 'close' : 'open'}</Text> : null}
</View>
{open &&
child.map((x, i) => {
if (x.child.length) {
return (
<Item
key={x}
active={childActive}
i={i}
setActive={setChildActive}
child={x.child}
/>
);
}
return (
<Text key={x} style={styles.subItem}>
- SOME DATA
</Text>
);
})}
</TouchableOpacity>
);
}
It's a completely dynamic process, you can extend the chain as long as you want. You can also check out the expo to look at its works.
https://snack.expo.dev/#akash0208/forlorn-popsicle

Change only one icon in a list of objects - React Native

I want to be able to change the icon in a list of todos (see picture) from an exclamation mark, to a checkmark. That should happen if the user puts the finger on the icon, or the developer clicks with the mouse in the emulator.
Through the code below, I manage to change it, but the new icon only appears if I close the modal containing the list, and reopen it. So the modal does not re-render, neither partly nor in whole.
How can I make the changes appear live, immediately after I click the exclamation icon? I suspect it has to do with state, but it doesn't seem possible to create a React hook inside the map function. If I let onPress call a function, then the state is only known within that external function, and I don't know how to export it.
export const TeacherMessages = (props) => {
return (
<View
style={[
styles.borderBox,
props.todos.length > 0 || props.notes.length > 0
? styles.whiteBox
: null,
]}
>
{
props.todos.map((todo) => (
<View key={todo.id} style={styles.listNotes}>
<AntDesign
style={styles.listIcon}
onPress={() => todo.isChecked = true}
name={todo.isChecked ? "checksquare" : "exclamationcircle"}
color={todo.isChecked ? "green" : "red"}
size={18}
/>
<Text style={styles.listText}> {todo.description}</Text>
</View>
))
}
);
I think you need to store the todos array in a react hook so that way the changes you do to it becomes live instantly, You can have this changeTodo function in the parent component and pass it as props to call it from the child component with the index needed. I think this might help:
export const TeacherMessages = (props) => {
const [todosArr, setTodosArr] = React.useState(props.todos)
const checkTodo = (todoIndex) =>{
let arr = [...todosArr]
arr[todoIndex].isChecked= true
setTodosArr(arr)
}
return (
<View
style={[
styles.borderBox,
todosArr.length > 0 || props.notes.length > 0
? styles.whiteBox
: null,
]}
>
{
todosArr.map((todo, index) => (
<View key={todo.id} style={styles.listNotes}>
<AntDesign
style={styles.listIcon}
onPress={() => checkTodo(index)}
name={todo.isChecked ? "checksquare" : "exclamationcircle"}
color={todo.isChecked ? "green" : "red"}
size={18}
/>
<Text style={styles.listText}> {todo.description}</Text>
</View>
))
}
);

How do I flex horizontally within a column layout? Currently only half the width of the screen

How do I flex horizontally within a column layout? Currently only half the width of the screen
Here's the current render code:
render() {
const {handle_data} = this.state
if (handle_data.length) {
return (
<ScrollView style={styles.container}>
{
handle_data.map(pack => {
console.log('this.cleanDataForFlatlist(pack): ', this.cleanDataForFlatlist(pack))
return (
<View style={styles.pack}>
<Text style={{fontWeight: 'bold'},{fontSize: 24}}>{pack[0].pack_name}</Text>
<Text style={{fontWeight: 'bold'},{fontSize: 16}}>{pack[0].pack_description}</Text>
<View style={styles.pack}>
<FlatList
data={this.cleanDataForFlatlist(pack)}
keyExtractor={this._keyExtractor}
renderItem={this._renderHandle}
/>
</View>
</View>
)
})
}
</ScrollView>
);
} else {
return null
}
}
Here's what it looks like now
I do not see your code about an item, so I show an example code. your problem is mainly the item render.
// the soruceData is the data souce,in other words, your handle_data
<View style={{flex:1, backgroundColor:'transparent'}} >
<FlatList
data={this.state.sourceData}
renderItem={this._renderItem}
/>
</View>
//the render item method, I suggest you put the item into a pure component
_renderItem = ({ item }) => {
return (
<View style={{flexDirection:'column',width:'92%'}>
id={item.id} // the every item should hava a unique id or key
<ImageView source={{uri:"image url"}} style={{width:,height:}}/>
<Text numberOfLines={1} style={{fontSize: 18,
color: '#353535',}}>"content"</Text>
/>
);
};
you set layout width or height should use flex or percent. in this case, your UI is flexible.
At the same time, I suggest you read felx layout guide
In the end, I suggest you remove the scroll view, move views which algin above the flatList into the flatList ListHeaderComponent. it can avoid the scroll conflict.

ReactNative - FlatList not updated until scroll

I have a problem with FlatList component which does not update until scrolled.
I tried add log to renderItem and keyExtractor both methods called with correct data but list didn't update.
Here is a render method:
render() {
const messages = this.props.messages
const message = this.props.message
return (
<View style={[styles.container]}>
<FlatList
ref={"flatList"}
contentContainerStyle={styles.list}
data={messages}
renderItem={(listItem) => {
return <MessageBuble message={listItem.item}/>
}}
keyExtractor={(item: Message) => {
return item.id
}}
/>
<View style={[styles.textInputContainer]}>
<TextInput
style={styles.textInput}
value={message}
multiline={true}
onChangeText={this.props.messageChanged}
/>
<Button title={"Odeslat"} onPress={() => {
if (this.props.sendMessage) {
this.props.sendMessage(this.props.message)
}
}}/>
</View>
</View>
)
}
Add extraData in FlatList and retry
<FlatList
extraData={this.props}
....
Tried the extraData, but that does not work.
There was an issue on Android where content was not visible when I returned back from another page to home screen (where the flatlist was present). The content was visible when I scrolled it a bit.
I assigned the main list to the extraData attribute, and could see that it changed in size via console logs. But the content remained invisible. Finally, used
onContentSizeChange={() => {
if (list.length > 0) {
ref.current.scrollToOffset({ animated: true, x: 0 });
}
}}
and it worked.