I'm trying to build a Chat App with React Native.
In the chat screen, there is a ScrollView for every message sent and received.
However, every time when I leave the screen and back in, the default of the ScrollView is at the top. I've written a function to scrollToEnd after rendered, but that is After Rendered, I need to set the ScrollView at the bottom before it actually shows.
I've tried (but users can see it scrolling as soon as it is rendered):
const scrollToBottom = () => {
scrollEl.current.scrollIntoView({ behavior: 'smooth'})
}
useEffect(() => {
scrollToBottom()
}, [scrollEl, loading, chatHistory])
return(
<ScrollView bottom={0} w="100%" bg="blueGray.100" inverted>
<VStack flex={1} w="100%" id="VStack">
{!loading && chatHistory.chats[roomId] && chatHistory.chats[roomId].length > 0 && chatHistory.chats[roomId].map((chat, index) =>
<ChatItem type={chat.type}
text={chat.text}
fromSelf={chat.fromSelf}
key={index}
/>
)}
<Box ref={scrollEl} />
</VStack>
</ScrollView>
)
I've tried:
Set scale: -1 at CSS, which actually invert the whole screen upside down.
Thus, I have to invert every single message to invert them back to the normal side.
Meanwhile, the scrolling is totally in the opposite way when you scroll by mouse or slide on the screen.
Any solutions or suggestions?
Thanks!
I hardly recommend you to use FlatList cause of the performance of the rendering list
https://reactnative.dev/docs/flatlist
Also in FlatList you can achieve what exactly you want
https://reactnative.dev/docs/flatlist#initialscrollindex
Even if you want better performance below library handle list better than FlatList
https://github.com/Flipkart/recyclerlistview
Related
Consider the code below:
function Root()
{
const navigation = useNavigation();
return <>
<SideBar />
{navigation.state === 'loading'
? (
<div className="overlay"></div>
)
: (
<div className="main">
<Outlet />
</div>
)}
</>
}
My goal here is very simple: when the new page's data is loading, an overlay div is shown on the page until the loading completes.
Now the problem that I am facing is as follows: since the different pages of my SPA are just hardcoded pieces of text, not obtaining any information from the backend at all, there is absolutely zero latency in the navigation. This is good but the problem is that on such zero latency navigation, I get a blink overlay effect, which is undesirable.
What I want instead is to have a minimum delay for each navigation. That is, if the navigation actually completes in almost 0s, I'd still like to get the loading overlay to display for 500ms.
And if the navigation actually completes in, let's say, 3s, I'd like to get the loading overlay to display for 3s.
How can I do this?
I'm having the following design
Looking for a smart way to accomplish my mission using #react-navigation/native-stack
Requirements are the following:
Filters should be present after native search bar.
Screen title should automatically shrink/expand upon scroll. As it's done by default.
The problem is I don't see any proper place to put filters component to.
If I put it to content title animation doesn't work. It's always small because content's outer component should be a ScrollView or its ancestor like FlatList or SectionList. Nested ScrollView of the same direction are not supported too.
<View>
<FiltersComponent />
<SectionList ... />
</View>
If I put it like that title animation works, but filters go out of bounds on scroll.
<SectionList ListHeaderComponent={FiltersComponent} ... />
I tried even following, but title looks buggy, its both presentations (small and large) visible in the same time.
// set headerLargeTitle: true initially
<SectionList
onScroll={(e) => {
navigation.setOptions({ headerLargeTitle: e.nativeEvent.contentOffset.y === 0 });
}}
/>
Can anyone suggest something?
I am using react and semantic. I am using the multiple sidebar example. The idea is that the left hand sidebar offers up some menu options, and then the right hand sidebar is the sub menu based on which option from the left menu is chosen. When a sub menu item is selected, a component is added to the Sidebar.Pusher, i.e displayed on the page.
It all works except re-rendering the content of the Sidebar.Pusher. This apparently only updates when the left hand side bar's visibility changes. I am using redux/rematch to handle state, and can see that the state that holds the content of the Sidebar.Pusher is being updated, but `render() is only being called when visibility changes of the sidebar.
The content of Sidebar.Pusher is an array, and I even tried displaying on the page the length of the array, which is being updated (pushed into) each time an item on the right hand sidebar is clicked. However this doesn't cause a render() to be fired, its literally when the left hand sidebar visibility changes.
Just to note, I did see this issue, however its from last year, and the answer wasn't enough for me to be able to fix the issue. Help would be appreciated.
Structure:
Index.js renders App.js, App.js renders Menu.js (which is a semantic set of tabs). One of the menu options is Sidebar.js which renders:
<Sidebar.Pushable as={Segment}>
<Sidebar
as={Menu}
animation="overlay"
direction="right"
inverted
vertical
visible={secondaryVisibility}
width="wide"
>
{focusedList.map((el, i) => {
return (
<Menu.Item key={i} as="a" onClick={() => this.addSegment(el)}>
<Article el={el} />
</Menu.Item>
)
})}
</Sidebar>
<Sidebar
as={Menu}
animation="overlay"
icon="labeled"
inverted
// onHide={this.handleSidebarHide}
vertical
visible={primaryVisibility}
width="wide"
>
<Menu.Item
onClick={() => this.changeTab(menuItem)}
as="a"
name="menuItem"
header
>
Menu Item
</Menu.Item>
</Sidebar>
<Sidebar.Pusher style={{ minHeight: "600px" }}>
<Segment basic>
{segments.map((el, i) => {
console.log(`el ${el}`)
return <Content key={i} segment={el} />
})}
</Segment>
</Sidebar.Pusher>
and all state (secondaryVisibility etc) is stored in rematch
Thanks
I haven't been able to identify the problem based on the code you've posted, could you provide more info such as the entire Sidebar.js and maybe what's in the Content component?. My guess would be that there's a HOC or lifecycle method getting in the way.
I've created a trivial example that seems to work fine, if I understand what you're trying to accomplish: https://codesandbox.io/s/myl6xpz9py
I got it. I forgot about immutability in state. Perhaps someone will benefit from this.
I was trying to update a state array with
let tmp = prevState.contract.segments
tmp.push(segment)
this.update({ segments: tmp })
However, this won't work as tmp is a reference to prevState.contract.segments, so this won't work, as pushing to tmp is equivelent to pushing to prevState.contract.segments.
you have to have a completely new array:
const tmp = [...prevState.contract.segments, segment]
this.update({ segments: tmp })
Now it works.
How to detect user finger lift off after scrolling? I've added pan responder with handlers on release is not firing.
With 'onMomentumScrollBegin' and 'onMomentumScrollEnd' props you can decide what to happen when the user scrolls the FlatList with hand.
userLiftedFingerOffScreen() {
// do whatever
}
render() =>
<FlatList
data={listData}
renderItem={({item}) => <SomeItem item={item} />}
onMomentumScrollEnd={this.userLiftedFingerOffScreen}
/>
I myself used this to make a slide show with Flatlist and it's pagination ability and I needed a non-automatic scrolling and onMomentumScroll was the solution.
If you just want to know WHEN the user has lifted their finger off the screen after scroll, you can use the onScrollEndDrag Function. FlatList iinherits props from ScrollView so all of ScrollView's props are accessible in a FlatList. You shouldn't need to use pan responder for this.
ex:
userLiftedFingerOffScreen() {
// do whatever
}
render() =>
<FlatList
data={listData}
renderItem={({item}) => <SomeItem item={item} />}
onScrollEndDrag={this.userLiftedFingerOffScreen}
/>
I made a component called IconButton that takes some props and passes any additional props through.
import Icon from 'react-native-vector-icons/FontAwesome';
const IconButton = ({ icon, title, ...props }) => {
console.log(props); // Actual: {}, expected: { onPress: [function] }
return (
<View style={ iconBox } { ...props }>
<Icon name={ icon } size={ 48 } color="black" />
<Text>{ title }</Text>
</View>
};
Then I rendered it:
const render = () => (
<IconButton icon='plus' title='add' onPress={ () => console.log('hi') } />
);
However when I tried to log it with console.log, onPress did not show up; it logged an empty object. Also, it wasn't passed to my View because it wasn't calling onPress when pressed. But when I pass different props with different types such as numbers and strings, it shows up fine.
Why isn't it being passed to my View and why isn't the prop being logged? I'm also using Expo if that may affect anything. I've set up an issue on GitHub.
The logging issue is a bug with Expo, which uses JSON.stringify and other methods to give an output, but isn't outputting objects correctly. The bug also encompasses functions such as onPress here which are part of objects, and causes console.log to not log it at all, seen here. The issue is on GitHub and is set to be fixed in the next SDK update on June 19, 2017.
Now onto a different issue,View doesn't take an onPress prop, it's not supposed to be touchable. To have a touchable view, try the following wrappers:
TouchableHighlight, on touch, there's a tint added to the view
TouchableNativeFeedback, Android only, regular view that responds to touches
TouchableOpacity, on touch, the view opacity is decreased
TouchableWithoutFeedback, on touch there's no visible feedback but accepts taps (you shouldn't use this, all interactions should have visible feedback for a good UX)