React-Native : useState to update object inside array - react-native

I have a list of items, when one is clicked, it navigates to a modal which displays a list of options.
I am trying to increment the counter inside each option, it works as intended BUT when I exit the modal screen and go back to it, the options counter are not reseted.
const myOptions = [
{ id: '001', name: 'option 001', counter: 0 },
{ id: '002', name: 'option 002', counter: 0 },
];
function ModalScreen({ route, navigation }) {
const [options, setOptions] = useState(myOptions);
let tempArr = [...myOptions];
// Array where I increment the counter, before passing it to setOptions(tempArr)
useEffect(() => {
return () => {
// because of let tempArr = [...myOptions]; changes in tempArr are copied
in myOptions. I want to reset myOptions when I exit the component
console.log(options);
console.log(myOptions) // both output are identical
};
}, []);
return (
<View>
<Text style={{ fontWeight: 'bold', marginBottom: 15 }}>
Click on an option to increment counter by 1
</Text>
<FlatList
keyExtractor={item => item.name}
extraData={tempArr}
data={options}
renderItem={({ item, index }) => (
<TouchableOpacity
onPress={() => {
tempArr[index].counter++;
setOptions(tempArr);
}}>
<Text>
{item.name} - counter: {item.counter}
</Text>
</TouchableOpacity>
)}
/>
</View>
);
}
I did a demo here :
https://snack.expo.io/#oliviermtl/carefree-marshmallows
I spent my day trying to figure out this one... Let me know if something needs more explanation
Thanks

Change
useEffect(() => {
return () => {
console.log(options);
console.log(myOptions)
};
}, []);
to
useEffect(() => {
return () => {
myOptions[0].quantity = 0;
myOptions[1].quantity = 0;
};
},[]);
What I was doing is that changing quantity values to 0, whenever user closes or comes back out from modal.
Hope this helps!

Related

Refresh FlatList React

the flatList after deleting work but not displayed on Screen items does't change
const App = () => {
const data = [
{ id: 1, text: "item One" },
{ id: 2, text: "item Two" },
{ id: 3, text: "item Three" },
{ id: 4, text: "item Four" },
];
const [state, setState] = useState(data);
const onPressItem = (id) => {
// code de delete item
const data = state;
alert(id);
const filterrArray = data.filter((val, i) => {
if (val.id !== id) {
return val;
}
});
console.log("filteerArray " , filterrArray)
setState( filterrArray)
};
const renderItem = ({ item }) => {
return (
<TouchableOpacity style={styles.item}>
<Text style={styles.text}>{item.text}</Text>
<Button title="Supprimer" onPress={() => onPressItem(item.id)} />
</TouchableOpacity>
);
};
const handleCheck = () => {
console.log('__________________________')
console.log(state)
}
return (
<View style={{ flex: 1, padding: 0, marginTop: "10%" }}>
<SafeAreaView>
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
</SafeAreaView>
<Button
title = "Verifier state"
onPress={handleCheck}
/>
</View>
);
};
i try the console log every time before the deleting and after the deleting is work fine but doest change on Screen or refresh automatically i dont know
not sure if I got your question right but if you want to update your flatlist, you need to pass state to data.
data={state}

Get position of individual Items in flatlist

I have flatlist horizontal like below
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad353abb28ba',
title: 'Fourth Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd291aa97f63',
title: 'Fifth Item',
},
{
id: '58694a0f-3da1-471f-bd961-145571e29d72',
title: 'Sixth Item',
},
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
const renderItem = ({ item }) => (
<Item title={item.title} />
);
return (
<SafeAreaView style={styles.container}>
<FlatList
horizontal
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
Whenever Item entered the viewport , I want to add animation to that element.I can get X and Y position of scroll with onScroll , now how do i get the positions of items to check if its in view port or if it went away from viewport...
Thank you.
Sorry for the late response. My pc has been super weird lately so when I encounter errors I have to second guess myself, and when nothing appears wrong, I second guess my pc (this time it was entirely me).
Here's my answer. I implemented the basic fade in/out [animation example][1] into the Item component. Whether it fades out or in is decided by the prop isViewable
// Item.js
const Item = (props) => {
const {
item:{title, isViewable}
} = props
/*
I copied and pasted the basic animation example from the react-native dev page
*/
const fadeAnim = useRef(new Animated.Value(1)).current;
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
useNativeDriver:false
}).start();
};
const fadeOut = () => {
Animated.timing(fadeAnim, {
toValue: 0,
duration: 1500,
useNativeDriver:false
}).start();
};
/* end of animation example*/
// fade in/out base on if isViewable
if(isViewable || isViewable == 0)
fadeIn()
else
fadeOut()
const animation = {opacity:fadeAnim}
return (
//add animation to Animated.View
<Animated.View style={[style.itemContainer,animation]}>
<View style={style.item}>
<Text style={style.title}>{title}</Text>
</View>
</Animated.View>
);
}
Create a FlatListWrapper (to avoid the onViewableItemChange on fly error). By doing this, as long as you don't make changes to FlatListWrapper, you wont get the on the fly error
// FlatListWrapper.js
const FlatListWrapper = (props) => {
// useRef to avoid onViewableItemsChange on fly error
const viewabilityConfig = useRef({
// useRef to try to counter the view rerender thing
itemVisiblePercentThreshold:80
}).current;
// wrapped handleViewChange in useCallback to try to handle the onViewableItemsChange on fly error
const onViewChange = useCallback(props.onViewableItemsChanged,[])
return (
<View style={style.flatlistContainer}>
<FlatList
{...props}
horizontal={true}
onViewableItemsChanged={onViewChange}
/>
</View>
);
}
const style = StyleSheet.create({
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'50%',
height:40
},
// main FlatList component
const FlatListAnimation = () => {
// store the indices of the viewableItmes
const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
return (
<SafeAreaView style={style.container}>
<FlatListWrapper
horizontal={true}
//{/*give each data item an isViewable prop*/}
data={DATA.map((item,i)=>{
item.isViewable=viewableItemsIndices.find(ix=>ix == i)
return item
})}
renderItem={item=><Item {...item}/>}
keyExtractor={item => item.id}
onViewableItemsChanged={({viewableItems, changed})=>{
// set viewableItemIndices to the indices when view change
setViewableItemsIndices(viewableItems.map(item=>item.index))
}}
//{/*config that decides when an item is viewable*/}
viewabilityConfig={{itemVisiblePercentThreshold:80}}
extraData={viewableItemsIndices}
/>
{/* Extra stuff that just tells you what items should be visible*/}
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map(i=><Text> {DATA[i].title}</Text>)}
</SafeAreaView>
);
}
const style = StyleSheet.create({
container:{
padding:10,
alignItems:'center'
},
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'50%',
height:40
},
item:{
borderWidth:1,
padding:5,
},
itemContainer:{
padding:5,
}
})
By wrapping your FlatList in a separate file, you wont encounter the "onViewableItemsChange on the fly" error as long as you dont modify FlatListWrapper.js
[1]: https://reactnative.dev/docs/animated
Use onViewableItemsChanged this is called when the items in the flatlist changes.
const handleViewableItemsChanged = (viewableItems, changed) => {}
<Flatlist
...
onViewableItemsChanged={handleViewableItemsChanged}

How to count a persentage of a painted area of the screen?

My question in one picture
There is a screen in my project where you able to draw on the screen; the goal of the screen - to draw over full screen in time. It was pretty hard to get paint part to work. but now its even harder - i dont know how to count the percent of painted area of the screen and show the result on the screen, at the same time changing it while painting on the screen.
I believe the right way to do so - to initialize a "var" and somehow make it depend on the area painted... But i still dont get the right way to do smth like this.
My project consists of two parts - absolute and block section. I will attach the parts of the both sections, so you can see how the painting part is done and so counting part (not done).
The absolute part is (do not included css);
type PropsType = {
blocksCount: number;
selectedIndexes: Array<number>;
setSelectedIndexes: (newSelectedIndexes: Array<number>) => void;
};
const AbsoluteSection: React.FC<PropsType> = (props) => {
const [counter, setCounter] = useState(60);
const navigation = useNavigation();
const percent = Math.round(
(props.selectedIndexes.length / props.blocksCount) * 100,
);
counter > 0 && setTimeout(() => setCounter(counter - 1), 1000);
const navigate = (isFail?: boolean) => {
navigation.navigate('TestResultScreen', {
title: 'Мультитач',
isSuccess: isFail,
});
props.setSelectedIndexes([]);
setCounter(60);
};
useEffect(() => {
counter <= 0 && navigate(true);
}, [counter]);
useEffect(() => {
if (props.selectedIndexes.length >= props.blocksCount) {
navigate();
}
}, [props.selectedIndexes]);
return (
<View style={styles.absolute_wrap} pointerEvents={'none'}>
<Text size={24} color={'#AAAAAA'} isCenterAlign>
{props.selectedIndexes.length <= 0 &&
'Починай замальовувати пальцем екран'}
</Text>
<View style={styles.pie_wrap}>
<Pie
radius={80}
innerRadius={75}
sections={[
{
percentage: percent,
color: '#FFC107',
},
]}
backgroundColor="#ddd"
/>
<View style={styles.gauge}>
<Text style={styles.gaugeText} size={28}>
{percent}%
</Text>
</View>
</View>
<Text size={28}>{counter} сек</Text>
</View>
);
};
The block part (more to painting) is:
type PropsType = {
isSelected: boolean;
onPress: () => void;
};
const BlockItem: React.FC<PropsType> = (props) => {
return (
<TouchableOpacity
onPress={props.onPress}
style={[
styles.block,
{
height: '8%',
width: '16.6%',
backgroundColor: props.isSelected ? '#F6CE0E' : 'white',
},
,
]}
/>
);
};
and
type PropsType = {
isSelected: boolean;
onPress: () => void;
};
const BlockItem: React.FC<PropsType> = (props) => {
return (
<TouchableOpacity
onPress={props.onPress}
style={[
styles.block,
{
height: '8%',
width: '16.6%',
backgroundColor: props.isSelected ? '#F6CE0E' : 'white',
},
,
]}
/>
);
};
I hope for best and i will appreciate help a lot, since i trying to solve this thingy for a quite some time now and its not getting any easier)

How to refresh/re-render flatlist on react-native?

im trying to refresh my flatlist from some page without going back to the principal menu, but it doesnt work.
I've already readed about extraData, but it doesnt work either.
Basiclly my program is like that:
I have a page called "passwords" and i add some passwords there from another page called "add passwords". When i click to add a password, i want to refresh the flatlist from the page "passwords" to show me the password that i just added.
This is my code from the page "add passwords"
...
state = {
arr: [],
local: '',
password: '',
obj: {
local: '',
password: ''
},
count: 1,
texto: ''
};
componentDidMount() {
//Here is the Trick
const { navigation } = this.props;
//Adding an event listner om focus
//So whenever the screen will have focus it will set the state to zero
this.focusListener = navigation.addListener('didFocus', () => {
this.setState({ count: 0 });
});
}
storeItem(item) {
try {
//we want to wait for the Promise returned by AsyncStorage.setItem()
//to be resolved to the actual value before returning the value~
console.log(item)
var joined = this.state.arr.concat(item);
console.log(joined)
this.setState({ arr: joined })
AsyncStorage.setItem('array', JSON.stringify(joined));
console.log(this.state.arr)
} catch (error) {
console.log(error.message);
}
}
componentWillMount() {
AsyncStorage.getItem('array').then(array => {
item = JSON.parse(array)
array ? this.setState({ arr: item }) : null;
console.log(item)
})
}
render() {
return (
<View style={styles.container}>
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={(text) => this.setState({ local: text })}
value={this.state.local}
/>
<TextInput
secureTextEntry={true}
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={(text) => this.setState({ password: text })}
value={this.state.password}
/>
<Button title='Adicionar'
onPress={() => this.storeItem({ local: this.state.local, password: this.state.password }) + alert("Adicionado com sucesso!") + this.props.navigation.navigate('Passwords')}
></Button>
</View>
);
}
}
And this is my page "passwords" where i want to refresh
componentWillMount() {
const { navigation } = this.props;
this.willFocusListener = navigation.addListener(
'willFocus',
() => {
this.setState({ count: 10 })
}
)
AsyncStorage.getItem('array').then(array => {
item = JSON.parse(array)
item ? this.setState({ arr: item }) : null;
console.log(this.state.arr)
})
}
renderItem = ({ item }) => (
<View style={{ flexDirection: 'row' }} style={styles.passwordContainer}>
<Text> {item.local} </Text>
<Text> {item.password} </Text>
</View>
)
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.arr}
renderItem={this.renderItem}
extraData={this.state} //this is what i tryied
/>
</View>
);
You can use your listener to update the state.
componentWillMount() {
this.willFocusListener = navigation.addListener(
'willFocus',
() => this.updateData()
}
updateData = () => {
this.setState({ count: 10 });
AsyncStorage.getItem('array').then(array => {
item = JSON.parse(array)
item ? this.setState({ arr: item }) : null;
console.log(this.state.arr)
});
}
Any state changes will rerender items.

Set state for element in array [react-native]

I have a view
renderFrame = () => {
const frames = []
for(let i=1; i<=10; i++) {
frames.push (
<TouchableOpacity style={this.state.isActive ? styles.frameItemActive : styles.frameItem} key={i} onPress={this.handleChoiceTime.bind(this, i)}>
<Text style={{color: '#8E8E93', fontSize: 17}}>{i}</Text>
</TouchableOpacity>
)
}
return frames
}
how to set state for element in frame, default is false, when click then is true, double click then is false
Try the below code.
I have kept the frames outside the render method and used flatlist. Now When the button is pressed the isFrameActive flag of that particular frame element will change and re-render. So this should work as per your requirement. Give a try!
this.state = {
frames : [
{
id: 1,
value: 1,
isFrameActive: true
},
{
id: 2,
value: 2,
isFrameActive: true
},
{
id: 3,
value: 3,
isFrameActive: true
}
]
};
handleChoiceTime = (index, item) => {
this.state.frames[index].isActive = !this.state.frames[index].isActive;
this.setState({
frames: this.state.frames
})
}
renderFrame = (item) => {
<TouchableOpacity style={item.isFrameActive ? styles.frameItemActive : styles.frameItem} key={item.id} onPress={this.handleChoiceTime.bind(index, item)}>
<Text style={{color: '#8E8E93', fontSize: 17}}>{item.value}</Text>
</TouchableOpacity>
}
render() {
<Flatlist
data={this.state.frames}
renderItem = {(item) => this.renderFrame(index, item)}
extraData={this.state}
/>
};