React native animation is cancelled once mobx array length is changed - react-native

I have a small app where I am testing the animations for the items. Basically when I swipe the item, it gets removed from the list and the animation is continued for like 0.5s. The problem is that the mobx updates the array when the item is removed from it and it cancels out the animation. So instead of animation continuing to show removal or addon effect, it just disappears quickly.
How do I avoid this? I can have timeouts to wait for the animation to finish and remove an item, but more importantly, the issue is the same if I fetch the data and the array is topped up, I get the same animation cancelling effect.
To show how I am rendering the components would be something like this:
private renderCards = () => {
const {fakeData} = this.props.mainStore
const el = []
fakeData.forEach(fd => {
el.push(
<Draggable key={fd.id}>
<ItemCard item={fd} />
</Draggable>,
)
})
return el
}
Example to replicate is here https://snack.expo.dev/Jh0Vk0cTX

Related

FlatList stop scroll when button press during scroll

i would like to know if there is any way to stop scroll event on iOs and Android
Steps to reproduce
scroll FlatList / ScrollView
click any button
already tried "onScroll" but when i set scroll ref.scrollTo it will scroll when stroll stops
Example GIF:
This is just an idea which I think might work, you may have to test it yourself.
Assuming you have the following state, controlling the modal.
const [open, setOpen] = useState(false);
You can probably add a onScroll event to Flatlist (onScroll is inherited from ScrollView), then
const onScroll = (e) => {
if (open) e.preventDefault();
}
If there is a delay before e.preventDefault is called, since setting state is async, you may want to use useRef() instead of useState() for checking if (open) since scroll happens quickly.
Another idea is to probably use a listener for the flat list, to listen to scroll, using same logic above.

react native navigation screen won't ReRender

when go from category to productList for the first time everything is right but after i came back and try to go for second time list screen won't rerender or refresh what ever you call. is there anyone has suggestions
The list Screen gets data on mount and that's it. If you want it to gets a new data every time the category name changes you have to add it to the useEffect dependencies like so.
const categoryName = navigation.route.params.categoryName;
useEffect(() => {
getData();
},[categoryName]);
Now every time categoryName changes getData gets called.
if you are using tabs just add unmountOnBlur: true, in the <Tab.Screen/> of the screen you are in

track UI elements states with one object, but the states are not reserved once leaving the screen and coming back

In my react-native project, I have three checkboxes, I need to track the state of those checkboxes so I use an object with key-value (value is boolean) to represent the states of all three checkboxes and use useState hook to manage them. Here is my code:
import React, { useState, useEffect } from 'react';
...
const MyScreen = ({ navigation }) => {
// initially, all checkboxes are checked
const initialCheckBoxState = {
0: true,
1: true,
2: true,
};
const [checkBoxesState, setCheckBoxesState] = useState(initialCheckBoxState);
useEffect(() => {
return () => {
console.log('Screen did unmount');
};
}, [checkBoxesState]);
return (
<View>
...
<SectionList
sections={options}
renderItem={({ index, item }) => (
<CheckBox
onPress={() => {
const checkBoxesStateCopy = { ...checkBoxesState };
checkBoxesStateCopy[index] = !checkBoxesStateCopy[index];
setCheckBoxesState(checkBoxesStateCopy);
}}
/>
)}
/>
...
</View>
);
};
I omitted code that is not the concern of my problem. As you can see, for each item I draw one CheckBox component.
In practice, there are always three items (i.e. three check boxes to show). At the beginning I declared initialCheckBoxState, each key-pair represents the state of the checkbox of each. In the onPress callback of Checkbox I toggle each check box state & update the checkBoxesState by hook method setCheckBoxesState as a whole.
Everything works fine at runtime, my screen is re-rendered when toggling checkbox state, UI shows the status of checkboxes correctly. But issue comes when I navigate back to the previous screen and navigate back to this screen, all checkboxes states are back to the initial states.
So, why the checkboxes states are not reserved?
P.S. previous screen and MyScreen are under the same stack navigator. User press a button of previous screen to navigate to MyScreen. From MyScreen user can go to previous screen by pressing the "headerLeft" button
First lets answer the question:
why the checkboxes states are not reserved?
This component is handling its state completely independent, the state is created & handled inside and no values are passed-in from outside. what does it mean? this component has its initial state value inside of itself, it doesn't use any prop or anything else to initialize the state. everytime this component gets created, state is again initialized with that value. so that's the reason you lose all changes done to checkboxes, because when you leave this screen(component) , it gets unmounted(we'll talk about this in next question) and because all values are just handled inside, every data (containing checkboxes state) will be lost.
So now lets talk about this:
is react-native supposed to reserve the state when come back to the screen?
short answer is No. Every component is destroyed when unmounted including their state and data.
Now lets answer why
screens are still on the stack in memory, not destroyed?
Usually developers use a package like react-navigation or RNRF(which is built on top of react-navigation) for react navigation, most of times we don't care about how they handle this navigation logic, we just use the interface the provided us. each of these packages may have their own way to handle navigation. providing full answer to determine why exactly the screen in still in memory needs full code review and sure lots of debugging but i guess there are 2 possibilities. first as i said maybe the package you are using keeps the unmounted screens in memory at least for a while for some reason. the 2nd is a common react community issue which is Unmounted component still in memory which you can check at: https://github.com/facebook/react/issues/16138
And at last lets answer the question:
how do i keep checkboxes state even with navigating back and losing component containing their state?
This doesn't have just one way to that but simple and short answer is move your state out of the that component, e.g move it out to the parent component or a global variable.
to make it more clear lets explain like this: imagine screen A is always mounted, then you go in B and there you can see some checkboxes and you can modify the states. if the state is handled completely inside B, if you navigate back from screen B to A you lose all changes because B is now unmounted. so what you should do it to put checkboxes states in A screen then pass the values down to B. and when modifying the values, you modify A state. so when B gets unmounted all changes are persistant because you have them in A.
other approached exists as well, you can create a global singleton object named globalState. then put values needed to share between multiple screens there. if you prefer redux or mobx you can use them. one of their usages is when you have some data that you need to share between mutiple screens, these data are independent from where you are at and will persist.
This explanation is from official react-navigation documentation:
Consider a stack navigator with screens A and B. After navigating to
A, its componentDidMount is called. When pushing B, its
componentDidMount is also called, but A remains mounted on the stack
and its componentWillUnmount is therefore not called.
When going back from B to A, componentWillUnmount of B is called, but
componentDidMount of A is not because A remained mounted the whole
time.
https://reactnavigation.org/docs/navigation-lifecycle/#example-scenario
Your MyScreen screen is equivalent to screen B from the example, which means you can expect your screen to stay mounted if you navigate forward, but not backwards.
Its simple, just add a keyExtractor to your SectionList component, which would uniquely identify each checkbox, so that react knows which one to re-render on update.
You'll want to use AsyncStorage to persist data to the device. State variables will be cleared any time the component unmounts.
AsyncStorage docs:
https://react-native-community.github.io/asaync-storage/
import AsyncStorage from '#react-native-community/async-storage';
//You can only store string values so convert objects to strings:
const storeData = async (value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem('#storage_Key', jsonValue)
} catch (e) {
// saving error
}
}
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('#storage_Key')
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch(e) {
// error reading value
}
}
UPDATE -
State is not being persisted due to the nature of React Component lifecycles. Specifically, when you navigate away from a screen the lifecycle method componentWillUnmount is called.
Here's an excerpt from the docs:
componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().
...Once a component instance is unmounted, it will never be mounted again.
This means any values stored in state will be destroyed as well and upon navigating back to the screen ComponentDidMount will be called which is where you may want to assign persisted values back to state.
Two possible approaches aside from AsyncStorage that may work for some use cases to persist data across screens is using Context or a singleton.

avoid scrolling to top after re-rendering

Why Vuejs scrolls to top of the page after re-rendering a component . It makes it feel like refreshing the page. How may I prevent this scrolling and having the page fixed in it's position
Imaging after clicking on some part it is going to re-render it's component
Here is the piece of code where I do the update , then scrolling to top happens
methods: {
updateData(data) {
let updatedCategory = data.category;
updatedCategory = new Category(updatedCategory);
updatedCategory.isDefault = 1;
for (let cat in this.categories){
if(this.categories[cat].id === updatedCategory.id){
Vue.set(this.categories,cat, updatedCategory);
}
}
}
},
Discover what element has the scroll bar.
Before updating your data, capture the ele.scrollTop.
Update your data.
In a nextTick, restore the element's scrollTop.
https://www.w3schools.com/jsref/prop_element_scrolltop.asp

React native synchronize two flatlist smoothly

I have two flatlist, one contains the data (dataScroll) and the other the checkboxes (checkScroll) for every data item.
The reason for this is that the checkboxes have to be always visible while the user scrolls horizontaly on the dataScroll (I put the dataScroll in a horizontally scrollable scrollview).
Demo:
expo snack demo
Tried so far:
On dataScroll's scroll event, I got the y offset and moved the checkScroll to that y position.
handleDataScroll = ({ nativeEvent: { contentOffset: { y } } }) => {
this.checkScroll.scrollToOffset({ offset: y, animated: true });
}
It (almost) does the job, but there is a huge delay between the 2 flatlist while scrolling.
--
I read that maybe the use of animated components the way to go, but I couldn't figure out how the animation works in react native.
So I'd like to get some help on how should I bind the two flatlist together so that if I scroll on one list, the other follows it with no (or at least minimal) delay.
If only the dataScroll flatlist is scrollable that's ok too.