React Native VirtualizedList get Scroll and scroll to position - react-native

So i am on RN 49.3,
I am looking for a way to get the scroll position or the index of the item visible on the virtualizedList!
PS: when i opened the source code of VirtualizedList.js which seems to have props.onScrollEndDrag & props.onScroll

We were using a different approach. It has changed a lot since, but I can help you with the initial approach. We added onScroll param to the list. We captured event.nativeEvent.contentOffset.y into the state or some variable inside spec. We used redux. When the user left the screen we saved this value inside a database. Second part was to load this value from the db in componentDidMount . You just put a ref into the list component and then call this.refs.myRef.scrollTo({ x: 0, y: loadedValue, animated: false });
Capture the scroll
render() {
return (
<VirtualizedList
ref='myRef'
onScroll={event => {
this.scroll = event.nativeEvent.contentOffset.y;
}}
...
/>
);}
Save on exit
componentWillUnmount() {
AsyncStorage.setItem(key, this.scroll);
}
Load after mounting
componentDidMount() {
AsyncStorage.getItem(key)
.then(y => {
this.refs.myRef.scrollTo({ x: 0, y, animated: false });
});
}
Best approach I think is to handle it inside redux and you connect this component with the list to the store. I mentioned saving into db because we do save the position for later use, this is only optional and depends on your requirements.
You may use the state as well, but then you need to handle the unnecessary updates

Related

Struggling with useEffect and flatlist

I am rendering a component for every item in a flatList. Each component has a label, and when the component is rendered, I have a useEffect that fetches the updated label name for that specific label.
For some reason, it seems to only be running for the last item in the flatList. The last item is the only item with the updated name, while all other still contain the outdated information.
Assuming there is an updated name for each label, why could my useEffect only be running on the last item?
<FlatList
data={labels}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
Label.js - I would think this would run for every label component rendered. Could there be a possible issue with what I have here? Or must it be somewhere else in my code?
let name = label.name;
useEffect(() => {
updateLabel()
name = label.name
}, [label]);
return (
<>
{name}
</>
)
I see several possible issues. Some important code is missing, so I'll answer what I can.
You're not using state to hold your label name in the Label component (name = label.name), so React will never know to re-render the component when it changes. It's rare to need to use a let variable in React. To hold properties that the component needs to change, use the useState hook.
However, you shouldn't do that here, because of the next point.
It looks like you are updating the label somewhere else, and also locally (name = label.name). Don't do this, it's too easy for the two to get out of sync and cause bugs. If the name is coming from somewhere else, show it and set it from props.
I'm not sure what updateLabel() does or where it comes from (how does the function know what to update the label to?), but if you need it, it should come from props.
If label.name is a string, you can't render it in a fragment. You must render it in a Text component. <Text>{label.name}</Text>
The object that FlatList passes in to the renderItem callback does not have a property called label, you are looking for item - this is the object from the data prop.
function renderLabel({ item }) { // item, not label
return <Label label={item} onPress={() => onPressLead(item)}/>;
}
const Label = ({ label, updateLabel }) => {
// no local label variable
useEffect(() => {
updateLabel(); // what is this supposed to do?
}, []); // no dependencies, if you only want to update the label once on mount
return <Text>{label.name}</Text>; // if label.name is a string
};
// your FlatList is fine as written
Your use effect probably needs the label as a dependency.
useEffect(() => {
updateLabelName()
}, [label]);

Is there a way to animate the increased size of a View when new children are added?

I’m currently using LayoutAnimation to animate a view when children are added. However, since LayoutAnimation causes everything to be animated, globally, and I can’t easily use built-in Animated library to fit my use-case, I’m wondering if react-native-reanimated is able to help.
Here's a snack of my current solution:
https://snack.expo.io/#insats/height-adapation
This is what the result of that looks like:
Is there a way to achieve the same thing without using LayoutAnimation? I've looked through all exampled in react-native-reanimated, and I've read through the docs but I'm still not sure if this is possible to do or how I should get started. I've thought about using Animated to move the item-wrapper out of the viewable area and "scroll" it upwards (using transform translateY) when items are added, but that would require fixed height, which I don't have.
I have 2 approaches that I can suggest out of my mind:
You can configure your LayoutAnimation only when your desired state changed. If you use hooks it would be too easy:
const [state,setState] = useState([]);
useEffect(()=>{
/*rest code*/
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
},[state])
Or if you use class component you can catch your desired state change in componentDidUpdate:
componentDidUpdate(prevProps,prevState){
if(prevState.items!==state.items){
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
}
}
You can use onLayout function of view:
addItem = () => {
this.setState({
items: [...this.state.items, {title:'An item',isNew:true}]
})
};
renderItems = () => {
return this.state.items.map((item, index) => {
let opacity = new Animated.Value(0);
return (
<Animated.View onLayout={({nativeEvent})=>{
if(this.state.item.isNew){
// here you got the height from nativeEvent.layout.height
// Then you have to store the height animate height and opacity to its precise value
// PS I used opacity:0 to calculate the height
}
}} key={index} style={[styles.item,{opacity}>
<Text>{item.title}</Text>
</View>
)
});
};
When it comes to react-native-reanimated I regard it as more faster version of react-native's Animated library. So either way you will have to calculate the height!

React Native state update during animation "resets" the animation

I am facing a problem that I've tried to solve in lots of different ways, but I cannot get it to work. Please see this Expo application, I've created a dumb example that demonstrates my problem: https://snack.expo.io/HJB0sE4jS
To summarize, I want to build an app with a draggable component (The blue dot in the example), but while the user drags the component I also need to update the state of the app (the counter in the example). The problem is that whenever the state updates during dragging, the component resets to it's initial position. I want to allow the user to freely drag the component while state updates happen.
I was able to "solve" the issue by putting the PanResponder in a useRef, so it won't be reinitialized in case of a state update, but as you can see in the example, I want to use the state in the PanResponder. If I put it in a useRef I cannot use the state in the PanResponder because it will contain a stale value (it will always contain the initial value of the counter which is 0).
How do you handle these kind of situations in react native? I guess it is not too uncommon that someone wants to update the state during an animation, although I cannot find any documentation or examples on this.
What am I doing wrong?
Edit: I was investigating further and I can see that the problem is that I'm mapping the (dx,dy) values from the gesture parameter to the position, but the (dx,dy) values are reset to (0,0) when the state changes. I guess (dx,dy) initialized to (0,0) when PanResponder is created. Still don't know what to do to make this work...
A mutable ref that holds the latest counter state value, along with a ref to prevent re-initializing the PanResponder should solve the problem in the example:
const [counter] = useCounter();
// Update the counterValue ref whenever the counter state changes
const counterValue = useRef(counter);
useEffect(() => {
counterValue.current = counter;
}, [counter]);
const position = useRef(new Animated.ValueXY());
const panResponder = useRef(PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: Animated.event(
[null, { dx: position.current.x, dy: position.current.y }],
{ listener: () => console.log(counterValue.current) } // counterValue being a ref, will not go stale
),
onPanResponderRelease: () => {
Animated.spring(position.current, { toValue: { x: 0, y: 0 } }).start();
}
})
);
You can check the above suggestion here: https://snack.expo.io/rkMLgp4jB
I understand though that this is a rather simplified example, and may not work for your actual use-case. It would help if you could share some more details on the actual usage!

React Native Multiselect

I am new to React Native
I am trying to create a multiselect view where user can select and deselect the items and then the selected items should pass back to the previous container and when user comes back to the next view the selected items should be checked.
I am trying to implement but getting the issue it is not updating data accurately. It shows only 1 selected item when I came back again to the screen.
Can anyone tell me the best way to do that or if there is any tutorial.
Should I do it with Redux or using react native?
Any help would be appreciated!!
Thanks!!
I believe the issue you describe is due to the following:
In componentDidMount you are calling updateItemWithSelected in a loop. This updateItemWithSelected call is both overwriting the checked attributes for all of the arrayHolder values on each call and also not using the updater function version of setState, so the later call of the loop may overwrite the earlier calls since setState is async and batched. If you are not using updateItemWithSelected elsewhere you should simplify componentDidMount to:
componentDidMount() {
const selectedTitles = {};
const { state } = this.props.navigation
const params = state.params || {};
if (params.data.length){
params.data.forEach((element) => {
// create a map of selected titles
selectedTitles[element.title] = true;
})
}
const arrayHolder = this.array.map(item => {
// map over `this.array` and set `checked` if title is in `selectedTitles`
return {...item, checked: !!selectedTitles[item.title]};
});
this.setState({ arrayHolder });
}
and delete updateItemWithSelected.

React Native (Redux) FlatList jumping to top of list when onEndReached called

I have a FlatList in ReactNative which pulls a list of articles from an API. When the end of the list is reached on scrolling, a further page of articles is pulled from the API and appended to the articles list in a Redux reducer.
The FlatList is set up as:
render() {
return(
<FlatList data={this.props.articles.articles} // The initial articles list via Redux state
renderItem={this.renderArticleListItem} // Just renders the list item.
onEndReached={this.pageArticles.bind(this)} // Simply calls a Redux Action Creator/API
onEndReachedThreshold={0}
keyExtractor={item => item.id.toString()}/>
)};
The Redux 'articles' state object is mapped to the component using the React Redux connect function. The relevant reducer (some items removed for brevity) looks like:
/// Initial 'articles' state object
const INITIAL_STATE = {
articles: [],
loading: false,
error: '',
pageIndex: 0,
pageSize: 10
};
export default(state = INITIAL_STATE, action) => {
switch(action.type){
// The relevant part for returning articles from the API
case ARTICLES_SUCCESS:
return { ...state, loading: false, articles: state.articles.concat(action.payload.items),
pageIndex: action.payload.pageIndex, pageSize: action.payload.pageSize}
default:
return state;
}
}
The payload is a simple object - the articles are contained in the action.payload.items array, and there is additional paging information as well in the payload (not important to this problem).
Everything is fine with the API returning the correct data.
The problem is that when the end of the FlatList is reached on scrolling, the API is called, the new articles are pulled fine and appended to the this.props.articles state object and to the FlatList, BUT the FlatList jumps/scrolls back to the first item in the list.
I think the problem is probably with how I am returning the state in the Redux reducer but I'm not sure why.
Is using concat the correct way to return the new state with the new articles appended?
It might be too late to answer this but I experienced exact same problem having my data served from redux to the component and managed to fix the jumping effect by making my component a normal Component rather than PureComponent allowing me to use shouldComponentUpdate life-cycle hook method. This how the method should look like for you component:
shouldComponentUpdate(nextProps) {
if (this.props.articles === nextProps.articles) {
return false;
}
return true;
}
This method (if implemented) should return a boolean value indicating whether the component should update or not. What i did here was to prevent the component from re-rendering in case the contents of articles hasn't changed.
Hope this would be helpful for you or others who might have faced similar problem.
You need to add to listItem styles height and width. Freezing and jumping maybe because list tried to calculate sizes for all items even if they dont display.
And you can find answer in: https://github.com/facebook/react-native/issues/13727
Using FlatList as component and adding data as props re-renders whole content, so it scrolls to top. I recommend using it directly inside your page and changing its data using this.state, not props