How to animate multiple entries using react native reanimated v2? - react-native

I am new to reanimated. I am now trying to reanimating multiple items.
These items will not show up at the start of the component on load.
But they items will show up when the corresponding item is pressed that my intentions.
eg when button 1 is pressed item 1 will pop up slowly, button 2 is pressed and item 2
like that.
I got 2 component in my app
ItemsList screen and item component
I don't have any animation code in itemList screen. I am just retuning the item component.
{items.map(item => (
<OtherItem
key={item._id}
item={item}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
/>
))}
Inside the item component.I got a sharedValue, useEffecthooks, and useState which I use to animate according to user interaction.
ITEM COMPONENT
const [selected, setSelected] = useState(false);
const [count, setCount] = useState(1);
// Animation
const progress = useSharedValue(0);
const reAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: progress.value,
transform: [{scale: progress.value}],
};
});
useEffect(() => {
progress.value = withTiming(1, {duration: 2000});
}, [selected]);
return (
<TouchableOpacity
onPress={() => selectItem(item)}
style={[
globalStyle.pageContainer,
]}>
{selected && (
<Animated.View
style={[
{flexDirection: 'row', alignItems: 'center'},
reAnimatedStyle,
]}>
...
</Animated.View>
)}
</TouchableOpacity>)
As you can see in the code, My intention is that when user press button 1 hidden details inside button 1 will show up.
But the thing Is only the first time works. I think its because of the shared value. What I want is I want every item to work. So can any one suggest the solution

Here is one of many solutions.
Track the selected item on "ItemsList screen" using the following snippet
const [selectedId, setSelectedId] = useState(null);
const handleSelection = (id) => setSelectedId(id);
return (
<SafeAreaView style={styles.container}>
{ITEMS.map((item) => (
<OtherItem
key={item._id}
item={item}
handleSelection={handleSelection}
selectedId={selectedId}
/>
))}
<StatusBar style="auto" />
</SafeAreaView>
);
}
on "ITEM COMPONENT" screen, use useEffect to change progress.value. If the "ITEM COMPONENT" sees that the current rendered item is selected then it will increase the progreass.value 1 other wise it will decrease it to 0.
use the following snippet
useEffect(() => {
if (selectedId === item._id)
progress.value = withTiming(1, { duration: 2000 });
else progress.value = withTiming(0, { duration: 2000 }); // un comment this line if you want to see hidden element of just one item and hide the other item
}, [selectedId]);
also send a function ( handleSelection in this example ) from "ITEM LIST screen" to "ITEM COMPONENT" screen to track which item is selected.
Here is a expo snack with full source code.

Related

React native Flatlist not re-rendering on state change

I realize there are a lot of questions and answers about this out there but I am fairly new to react native and most of the answers are dealing with React Components and not hooks. In the following example availableInterests is pulled from a firestore database call. Then we loop through the availableInterests so the user can select the their interests from the Flatlist of interests. Everything works great except the FLatlist does not re-render so the button that is used to select currentInterests never shows the change that an interest has been selected. Does anyone see what I am missing here?
const [availableInterests, setAvailableInterests] = useState([]);
const [currentInterests, setCurrentInterests] = useState([]);
const selectThisInterest = (item) => {
let myInterests = currentInterests;
if(myInterests.includes(item.id)) {
myInterests.pop(item.id);
} else {
myInterests.push(item.id);
}
setCurrentInterests(myInterests);
}
return <View>
<Text style={styles.text}>Select Your Interests:</Text>
<FlatList
data={availableInterests}
keyExtractor={(item, index) => index.toString()}
extraData={currentInterests}
renderItem={({ item, index }) =>
<View key={item.id}>
<Text>{item.title}</Text>
<Text>{item.description}</Text>
<Image
source={{ uri: item.icon }}
style={{ width: 100, height: 100}}
/>
<TouchableOpacity onPress={() => selectThisInterest(item)}>
<Text style={styles.buttonText}>{`${currentInterests.includes(item.id) ? 'UnSelect' : 'Select'}`}</Text>
<Text>{item.id}</Text>
</TouchableOpacity>
</View>
}>
</FlatList>
</View>
put this state below
const [currentInterests, setCurrentInterests] = useState([]);
const [extra, setExtra] = useState(0);
at the end of your function just put this
const selectThisInterest = (item) => {
....
setExtra(extra + 1)
}
I think the mistake is in your selectThisInterest function. When you are updating the currentInterests based on previous value, React doesn't recognises such a change because you are simply assigning myInterests with your currentInterests.
What you want to do is to copy that array and assign it to myInteresets and then update your values to the new copied array. Once the calculation are completed on the new myInteresets array, the setCurrentInterests() will re-render the app because now React recognises there is a change in the state.
To copy the array, you can use,
let myInterests = [...currentInterests];
change your selectThisInterest function to reflect this change,
const selectThisInterest = (item) => {
let myInterests = [...currentInterests];
if(myInterests.includes(item.id)) {
myInterests.pop(item.id);
} else {
myInterests.push(item.id);
}
setCurrentInterests(myInterests);
}

Calling modal on a list of products opens the modal for all of them instead of just the one being clciked

I am making a react native app that loads data from google firebase and then display it on a page, when a user clicks on any of the products aa modal will open to show more datails.
I am using useEffect to load the data on page load then display then results:
const fetchData = async () => {
const categories = db.collection("productsDB");
const collections = await categories
.limit(6)
.onSnapshot((querySnapshot) => {
const items = [];
querySnapshot.forEach((documentSnapshot) => {
items.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setItems(items);
setLoading(false);
});
return () => collections();
};
useEffect(() => {
fetchData();
}, []);
and the show them like this:
{loading ? (
<ActivityIndicator />
) : (
items.map((item) => (
<TouchableOpacity
style={styles.queryResult}
key={item.key}
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<View style={styles.queryResultContent}>
<Image
style={{ width: 100, height: 100 }}
source={{ uri: String(item.images) }}
/>
<View>
<Text style={styles.queryInfoHeader}>{item.name}</Text>
</View>
</View>
<View>
<ProductModal
isModalVisible={modalVisible}
setModalVisible={setModalVisible}
navigation={navigation}
{...item}
/>
</View>
</TouchableOpacity>
))
)}
when I open the modal, it opens the modal for all of the products and doesnt really matter if I click on the first product or what, it opens all of the modals, and I am not sure how to get rid of this!
is there any better way to write this function?
You're using the same modalVisible flag for all of your modals; therefore, they either are all visible or all hidden.
Why not have a single modal rather than rendering a bunch of them in the loop, and pass the item as a prop to it?

OnPress change the style of component from loop- React Native with hooks

So I am pretty new in react native, I am trying to develop a quiz game, where users will be given Set of answers. I want to select change the color of the component when it is pressed by the user, kind of toggle it. So far I came up with useState solution, but unfortunately cannot figure out how to exclude the change of color, I guess I need to follow indexing or something, can anyone please make me understand the process with the solution.
export const QuizScreen = ({ navigation,route }) => {
const [quizArray, setQuizArray] = React.useState([])
const [rightAnswer, setRightAnswer]= React.useState(false)
const [selectBtn, setSelectBtn] = React.useState("#fff")
return(
<View>
{quizArray[qno].answer.map(r=>
<TouchableHighlight style={[styles.listItem, {backgroundColor:selectBtn}]}
onPress={()=>{
setRightAnswer(r.rightAnswer)
setSelectBtn("#DDDDDD") //so this changes logically all the component from the list
}}
activeOpacity={0.6} underlayColor="#DDDDDD"
>
<Text>{r.option}</Text>
</TouchableHighlight>
)}
</View>
I need to know how do i implement the background change for only one and kinda make it toggle everytime user select or deselect. Thank you
You were right about using an index for determining the clicked list item.
You can change the color by storing the index of the selected item using selectBtn state and then using that state set the backgroundColor accordingly.
Here is how you can do it:
export const QuizScreen = ({ navigation, route }) => {
const [quizArray, setQuizArray] = React.useState([]);
const [rightAnswer, setRightAnswer] = React.useState(false);
const [selectBtn, setSelectBtn] = React.useState(null);
return (
<View>
{quizArray[qno].answer.map((r, index) => (
<TouchableHighlight
style={[
styles.listItem,
{ backgroundColor: selectBtn === index ? '#dddddd' : '#fff' },
]}
onPress={() => {
setRightAnswer(r.rightAnswer);
setSelectBtn(index);
}}
activeOpacity={0.6}
underlayColor="#DDDDDD">
<Text>{r.option}</Text>
</TouchableHighlight>
))}
</View>
);
};
Here is the working example: Expo Snack
2

How to change particular index image in flatlist renderitem react native on conditional

I am showing some Audio data in Flat-list. Flat-list, I am showing in main class, But, RenderItem calling in separate class. So, Once I tapped particular row item, I am playing audio file. But, I have to change pause to play image. But, When I tried to change it, All images are getting changes.
Bydefault, I am showing all cells images with pause icon.
Also Once user taps on play/pause in audio player, Then I have to change flatlist current playing item row images either play/pause.
I am showing audio player in bottom of the screen. Once user tap on flatlist pause icon, I am playing audio player in bottom of the screen.
I have tired but, All cells images getting changing.
Any suggestions?
Note: We have different UI for Audio player, So, I have created customized UI for player instead of default media component.
Main class.js
selectedAudio = (item, index) => {
if (isConnected) {
if (!isEmpty(audioURL)) {
// console.log('selected audio url is', audioURL);
SoundPlayer.playUrl(audioURL);
this.setState({
paused: false,
currentPosition: 0,
currentTime: 0,
audioSelectedIndex: index,
});
}
} else {
}
}
renderItem = ({ item, indexx }) => (
<Cell
item={item}
onSelected={this.selectedAudio}
index={indexx}
audioSelectedIndex={this.state.audioSelectedIndex}
/>
)
render() {
return (
<View some styles>
<FlatList
style={styles.faltList}
showsVerticalScrollIndicator
data={podcast}
extraData={this.state}
ItemSeparatorComponent={this.separator}
renderItem={this.renderItem}
/>
</View>
);
}
Cell.js
export default class Cell extends PureComponent {
render() {
const { item, indexx, audioSelectedIndex } = this.props;
return (
<View style={styles.flatListCell}>
<View style={styles.containerText}>
<Text style={styles.title}>
{item.title}
</Text>
</View>
</View>
<TouchableWithoutFeedback onPress={this.props.onSelected.bind(this, item)}>
<Image
style={styles.playPause}
source={audioSelectedIndex === indexx ? res.images.play : res.images.pause}
/>
</TouchableWithoutFeedback>
</ImageBackground>
</View>
);
}
}
The issue is that you are destructuring ({ item, indexx }), and renderItem doesn't pass indexx but index. Change indexx to index.
renderPodcastItem = ({ item, index }) => (
<Cell
item={item}
onSelected={this.selectedAudio}
index={index}
audioSelectedIndex={this.state.audioSelectedIndex}
/>
)
Second mistake, you are doing this const { item, indexx, audioSelectedIndex } = this.props; but you are not passing indexx but index to Cell. In Cell component change to.
const { item, index, audioSelectedIndex } = this.props;
Third mistake you are passing this.renderItem to renderItem but the function is undefined.
renderItem={this.renderPodcastItem}
DEMO

React native updates state "on its own"

I have two screens, one list (Flatlist) and one filter screen where I want to be able to set some filters for the list. the list screen has the states "data" and "usedFilters". When I am switching to the filters screen, the states are set as navigation parameters for react navigation and then passed via navigation.navigate, together with the onChange function, as props to the filter screen. There they are read, and the filters screen class' state is set (usually with passed filters from the list screen, if no valid filters has been passed, some are initialized).
After that the filters can be changed. If that happens, the state of the filter screen gets updated.
If then the apply button is clicked the filter screens' state is passed to the onChange function and via that back to the list screen, the onChange function updates the state "usedFilters" state of the list screen. If the cancel button is pressed null is passed to the onChange function and there is no setState call.
Setting new states for the list screen works perfectly fine. the problem is, that when i press the cancel button (or the back button automatically rendered by react navigation) the changes are kept nevertheless. That only happens if the state has been changed before. So if there has never been applied a change and hence the "usedFitlers" state of the list screen is null, this behavior does not occur. Only if I already made some changes and hence the "usedFitlers" state of the list screen has a valid value which is passed to the filters screen the cancel or go back buttons won't work as expected.
I am using expo-cli 3 and tried on my android smartphone as well as the iOS simulator. Same behavior. I looked into it with chrome dev tools as well but i simply couldn't figure out where the "usedFitlers" state was updated.
I am using react native 0.60 and react navigation 3.11.0
My best guess is that for some reason the two states share the same memory or one is pointer to the other or sth like that. (Had problems like that with python some time ago, not knowing the it uses pointers when assigning variables).
Anyone got an idea?
List Screen:
export default class ListScreen extends React.Component {
state = { data: [], usedFilters: null };
static navigationOptions = ({ navigation }) => {
let data = navigation.getParam('data')
let changefilter = navigation.getParam('changeFilter')
let currfilter = navigation.getParam('currFilter')
return {
headerTitle:
<Text style={Styles.headerTitle}>{strings('List')}</Text>,
headerRight: (
<TouchableOpacity
onPress={() => navigation.navigate('FilterScreen', {
dataset: data, onChange: changefilter, activeFilters:
currfilter })} >
<View paddingRight={16}>
<Icon name="settings" size={24} color=
{Colors.headerTintColor} />
</View>
</TouchableOpacity>
),
};
};
_onChangeFilter = (newFilter) => {
if (newFilter) {
this.setState({ usedFilters: newFilter })
this.props.navigation.setParams({ currFilter: newFilter });
} // added for debugging reasons
else {
this.forceUpdate();
let a = this.state.usedFilters;
}
}
_fetchData() {
this.setState({ data: fakedata.results },
() => this.props.navigation.setParams({ data: fakedata.results,
changeFilter: this._onChangeFilter }));
}
componentDidMount() {
this._fetchData();
}
render() {
return (
<ScrollView>
<FlatList/>
// Just data rendering, no problems here
</ScrollView>
);
}
}
Filter Screen:
export default class FilterScreen extends React.Component {
static navigationOptions = () => {
return {
headerTitle: <Text style={Styles.headerTitle}> {strings('filter')}
</Text>
};
};
state = { currentFilters: null }
_onChange = (filter, idx) => {
let tmp = this.state.currentFilters;
tmp[idx] = filter;
this.setState({ currentFilters: tmp })
}
_initFilterElems() {
const filters = this.props.navigation.getParam('activeFilters');
const dataset = this.props.navigation.getParam('dataset');
let filterA = [];
let filterB = [];
let filterC = [];
if (filters) {
// so some checks
} else {
// init filters
}
const filterElements = [filterA, filterB, filterC];
this.setState({ currentFilters: filterElements })
}
componentDidMount() {
this._initFilterElems()
}
render() {
const onChange = this.props.navigation.getParam('onChange');
return (
<ScrollView style={Styles.screenView}>
<FlatList
data={this.state.currentFilters} // Listeneinträge
keyExtractor={(item, index) => 'key' + index}
renderItem={({ item, index }) => (
<FilterCategory filter={item} name={filterNames[index]}
idx={index} onChange={this._onChange} />
)}
ItemSeparatorComponent={() => <View style=
{Styles.listSeperator} />}
/>
<View style={Layout.twoHorizontalButtons}>
<TouchableOpacity onPress={() => {
onChange(this.state.currentFilters);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('apply')} </Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => {
onChange(null);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('cancel')}
</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView >
);
}
}
So when I press the cancel button, null is returned to the _onChangeFilter function of the list screen. This part works, and according to console.log and the debugger, the setState is not called. But if i set a breakpoint within the else part, i can see that this.state.usedFilters has changed.
Ok after a while i figured it out. The problem was that the whole filters list was always just referenced since react native (js) seems to always use references, even when changing sub-parts of the lists.
fixed that by using lodash cloneDeep.