React Native Image Render Flatlist and Use State hook - reactjs

I have a screen in my ap, that's users upload photos. In this screen, photo can be selected from gallery or taken from camera. I have an array state where I keep the uri of photos. I show the photos in flatlist with this array state. Next to each photo there is an icon to delete. But the function called by this icon does not update the array state and delete the photo. Probably state updates asynchronously. Please help me!
const [images, setImages] = useState([]); //images array
const selectFile = async() => { //image from gallery
await launchImageLibrary(options, res => {
console.log('Response = ', res);
if (res.didCancel) {
console.log('User cancelled image picker');
}else {
let source = res;
setImages(prevImages=>([...images,source.assets[0].uri]));
console.log(images);
}
});
};
const takePicture = async () => { //take picture save cameraroll and get picture
try {
const options = {quality: 0.5, base64: false};
let imageUri;
const imageData = await camera.takePictureAsync(options).then(data => {
imageUri=data.uri;
});
await CameraRoll.save(imageUri, {type: 'photo'});
CameraRoll.getPhotos({
first:1,
assetType: 'Photos',
}).then((r)=>{
let imageUri=r.edges[0].node.image.uri;
setImages([...images,imageUri]);
});
console.log(images);
}catch (e) {
console.log(e);
}
};
const removeImage=async(item)=>{ //delete function
setImages(prevImages=>([...prevImages,images.filter(x=>x!==item)]));
alert(images);
}
return(
<FlatList
data={images}
keyExtractor={(item,index)=>item}
numColumns={2}
renderItem={({item,index})=>{
return (
<View style={{margin:2, borderWidth:2, borderColor:'rgba(255,255,255,0.42)',shadowColor: 'black',
shadowOpacity: 0.25,
shadowOffset: {width: 0, height: 2},
shadowRadius: 8,
overflow: Platform.OS === 'android' ? 'hidden' : 'visible',width:160,height:165,backgroundColor:'transparent'}}>
<Image
resizeMode="cover"
style={{
flex:1,
}}
source={{uri: item}}
/>
<TouchableOpacity
key={index}
underlayColor='transparent'
onPress={()=>removeImage(item)}
>
<AntDesign name={'minuscircle'} size={24} color={Colors.molekulRed} style={{marginLeft:'85%', marginTop:'-100%'}}/>
</TouchableOpacity>
</View>
}}
/>
)

try this,
const removeImage=async(item)=>{ //delete function
setImages(prevImages=>([...prevImages.filter(x=>x!==item)]));
alert(images);
}

Related

How to run a function constantly when focused on a react native navigation page

I am using react native sensors to detect gyroscope and accelerometer data about my phone, but I want to use both of these values together in unison for my app. I want the gyroscope value to be multiplied by the accelerometer data, and for this I need a continuous loop that runs about every 20ms. I want the function to only run when the page is open, to avoid lag and I want to to be access all the variables normal js code would be able to access (accelerometer data and gyro data).
Here is the function I have written already, but failed:
const [updateFunction, setUpdateFunction] = useState(0);
useEffect(() => {
setUpdateFunction(setInterval(() => {
console.log("Update")
}), 20);
return () => {
clearInterval(updateFunction);
}
})
Here is my full code:
import { View, Text, TouchableOpacity, Alert } from 'react-native';
import AppStyles from '../styles/styles'
import { Gyroscope, Accelerometer } from 'expo-sensors';
import { useState, useEffect } from 'react';
import * as React from 'react';
function Home(props) {
// Gyroscope Data
const [gyroData, setGyroData] = useState({
x: 0,
y: 0,
z: 0
});
// Accelerometer Data
const [accelData, setAccelData] = useState({
x: 0,
y: 0,
z: 0
});
const [accelSpeed, setAccelSpeed] = useState(0);
// Device Orientation
const [deviceOrientation, setDeviceOrientation] = useState({
x: 0,
y: 0,
z: 0
});
// Gyro Data
const [gyroSubscription, setGyroSubscription] = useState(null); // Gyroscope
const [accelSubscription, setAccelSubscription] = useState(null); // Accelerometer
Gyroscope.setUpdateInterval(1000);
Accelerometer.setUpdateInterval(100);
// Gyroscope
const _gyrosubscribe = () => {
setGyroSubscription(
Gyroscope.addListener(gyroscopeData => {
setGyroData(gyroscopeData);
})
);
};
const _gyrounsubscribe = () => {
gyroSubscription && gyroSubscription.remove();
setGyroSubscription(null);
};
const _accelsubscribe = () => {
setAccelSubscription(
Accelerometer.addListener(accelerometerData => {
setAccelData(accelerometerData);
setAccelSpeed(Math.floor(Math.abs(1 - Math.cbrt(Math.pow(accelerometerData.x, 2) + Math.pow(accelerometerData.y, 2) + Math.pow(accelerometerData.z, 2))) * 100));
})
);
};
const _accelunsubscribe = () => {
accelSubscription && accelSubscription.remove();
setAccelSubscription(null);
};
// Device Orientation Update Function
const [updateFunction, setUpdateFunction] = useState(0);
useEffect(() => {
setUpdateFunction(setInterval(() => {
console.log("Update")
}), 20);
return () => {
clearInterval(updateFunction);
}
})
// Page Navigation
const navigation = props.navigation;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'space-around', marginTop: 50 }}>
<Text style={AppStyles.homescreentitle}>Phone Soundboard</Text>
<View style={{height: "70%", width: "100%", alignItems: "center"}}>
<TouchableOpacity style={AppStyles.homebuttontouchableopacity} onPress={() => {
navigation.navigate("mouse");
}}>
<Text style={AppStyles.homebuttontext}>
Mouse
</Text>
</TouchableOpacity>
<TouchableOpacity style={AppStyles.homebuttontouchableopacity} onPress={() => {
navigation.navigate("test");
}}>
<Text style={AppStyles.homebuttontext}>
Test Section
</Text>
</TouchableOpacity>
<TouchableOpacity style={AppStyles.homebuttontouchableopacity} onPress={() => {
}}>
<Text style={AppStyles.homebuttontext}>
Check for Dev Update Func
</Text>
</TouchableOpacity>
<View>
<Text>
Device Orientation Data: x: {deviceOrientation.x} y: {deviceOrientation.y} z: {deviceOrientation.z}
</Text>
<Text>
Gyroscope Data: x: {gyroData.x} y: {gyroData.y} z: {gyroData.z}
</Text>
<Text>
Accelerometer Data: {
accelSpeed
}
</Text>
</View>
</View>
</View>
);
}
export default Home;
You don't need a seperate update function and loop thingy. Just put the code in a useEffect with the two vairables you're interested in, in the dependancy array.
E.g.:
const [multData, setMultData] = useState(0);
useEffect(() => {
const multData = /* fancy stuff */
setMultData(multData);
}, [accelData, gyroData]);
I've never used the sensor library, but it seems like you are already subscribing to their change, and they should update when they change.

Expo app keeps returning TypeError: undefined is not an object (evaluating 'function')

I am creating a mobile app that takes a picture and then returns the objects it detected. the way it works is that i have heroku running the ml model api. the weird thing about this error is that it happened after my code worked and sent an api request which then returned a json of what it detected. and when i ran it again it gave me [TypeError: undefined is not an object (evaluating '_expo.ImageManipulator.manipulateAsync')]. i looked at the value of image and asset to see if they are undefined and the console printed false. i even tried
console.log(typeof image==='undefined')
console.log(typeof asset==='undefined')
console.log(typeof image=='undefined')
console.log(typeof asset=='undefined')
just to make sure that the value of the variables aren't undefined. i then commented out the function to see what would happen and i got [TypeError: undefined is not an object (evaluating '_this.toServer')]. i then restarted the expo server and did everything i did above again but it keeps giving me [TypeError: undefined is not an object (evaluating '_expo.ImageManipulator.manipulateAsync')]. the code below is the code for the function that saves the image and makes a post request.
const takePicture = async () => {
if (cameraRef) {
try {
const data = await cameraRef.current.takePictureAsync();
console.log(data);
setImage(data.uri);
} catch (error) {
console.log(error);
}
}
};
uriToBase64 = async (uri) => {
let base64 = await FS.readAsStringAsync(uri, {
encoding: FS.EncodingType.Base64,
});
return base64;
};
const savePicture = async () => {
if (image) {
try {
const asset = await MediaLibrary.createAssetAsync(image);
console.log(image===undefined)
console.log(asset===undefined)
console.log(image==undefined)
console.log(asset===undefined)
let manipResult = await ImageManipulator.manipulateAsync(
asset,[{ resize: { width: 800, height: 800 } }],{ format: 'jpg' });
let assetResized = await MediaLibrary.createAssetAsync(manipResult);
await this.toServer({
type:"image",
base64: uriToBase64(assetResized),
uri: assetResized,
});
setImage(null);
console.log('saved successfully');
} catch (error) {
console.log(error);
}
}
};
also if i do:
if(!(image==undefined))
to make it so that if the image is undefined it won't do anything i get
LOG [Error: Argument of an incompatible class: class java.util.HashMap cannot be passed as an argument to parameter expecting class java.lang.String.]
WARN Possible Unhandled Promise Rejection (id: 0):
Error: Argument of an incompatible class: class java.util.HashMap cannot be passed as an argument to parameter expecting class java.lang.String.
promiseMethodWrapper#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:22934:45
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:114097:40
readAsStringAsync$#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:118249:92
tryCatch#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24610:23
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24590:34
tryCatch#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24610:23
invoke#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24648:30
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24672:19
tryCallTwo#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:30323:9
doResolve#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:30487:25
Promise#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:30346:14
callInvokeWithMethodAndArg#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24671:33
_invoke#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24676:157
async#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24762:69
readAsStringAsync#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:118236:38
_callee3$#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:120465:69
tryCatch#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24610:23
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24590:34
tryCatch#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24610:23
invoke#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24648:30
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24672:19
tryCallTwo#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:30323:9
doResolve#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:30487:25
Promise#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:30346:14
callInvokeWithMethodAndArg#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24671:33
_invoke#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24676:157
async#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24762:69
_callee3#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:120460:40
_callee5$#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:120529:36
tryCatch#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24610:23
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24590:34
tryCatch#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24610:23
invoke#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24648:30
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:24654:19
tryCallOne#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:30314:16
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:30415:27
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:31506:26
_callTimer#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:31406:17
_callReactNativeMicrotasksPass#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:31441:17
callReactNativeMicrotasks#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:31648:44
__callReactNativeMicrotasks#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:23414:46
http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:23193:45
__guard#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:23397:15
flushedQueue#http://192.168.1.4:19000/node_modules%5Cexpo%5CAppEntry.bundle?platform=android&dev=true&hot=false:23192:21
flushedQueue#[native code]
invokeCallbackAndReturnFlushedQueue#[native code]
here is the entire code:
import React, { useState, useEffect, useRef } from 'react';
import { Text, View, StyleSheet, TouchableOpacity, Image } from 'react-native';
import Constants from 'expo-constants';
import { Camera, CameraType } from 'expo-camera';
import * as MediaLibrary from 'expo-media-library';
import { MaterialIcons } from '#expo/vector-icons';
import Button from './src/components/Button';
import axios from 'axios'
import { Buffer } from "buffer";
import * as FS from "expo-file-system";
import { ImageManipulator } from 'expo';
export default function App() {
const [hasCameraPermission, setHasCameraPermission] = useState(null);
const [image, setImage] = useState(null);
const [type, setType] = useState(Camera.Constants.Type.back);
const [flash, setFlash] = useState(Camera.Constants.FlashMode.off);
const cameraRef = useRef(null);
useEffect(() => {
(async () => {
MediaLibrary.requestPermissionsAsync();
const cameraStatus = await Camera.requestCameraPermissionsAsync();
setHasCameraPermission(cameraStatus.status === 'granted');
})();
}, []);
const takePicture = async () => {
if (cameraRef) {
try {
const data = await cameraRef.current.takePictureAsync();
console.log(data);
setImage(data.uri);
} catch (error) {
console.log(error);
}
}
};
uriToBase64 = async (uri) => {
let base64 = await FS.readAsStringAsync(uri, {
encoding: FS.EncodingType.Base64,
});
return base64;
};
toServer = async (mediaFile) => {
url = "censored url so that trolls wont use it ";
let response = await FS.uploadAsync(url, mediaFile.uri, {
headers: {
"content-type": "image/jpeg",
},
httpMethod: "POST",
uploadType: FS.FileSystemUploadType.BINARY_CONTENT,
});
console.log(response);
};
const savePicture = async () => {
if (image) {
try {
const asset = await MediaLibrary.createAssetAsync(image);
console.log(typeof image==='undefined')
console.log(typeof asset==='undefined')
console.log(typeof image=='undefined')
console.log(typeof asset=='undefined')
await this.toServer({
type:"image",
base64: uriToBase64(asset),
uri: asset,
});
setImage(null);
console.log('saved successfully');
} catch (error) {
console.log(error);
}
}
};
if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
}
return (
<View style={styles.container}>
{!image ? (
<Camera
style={styles.camera}
type={type}
ref={cameraRef}
flashMode={flash}
>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 30,
}}
>
<Button
title=""
icon="retweet"
onPress={() => {
setType(
type === CameraType.back ? CameraType.front : CameraType.back
);
}}
/>
<Button
onPress={() =>
setFlash(
flash === Camera.Constants.FlashMode.off
? Camera.Constants.FlashMode.on
: Camera.Constants.FlashMode.off
)
}
icon="flash"
color={flash === Camera.Constants.FlashMode.off ? 'gray' : '#fff'}
/>
</View>
</Camera>
) : (
<Image source={{ uri: image }} style={styles.camera} />
)}
<View style={styles.controls}>
{image ? (
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 50,
}}
>
<Button
title="Re-take"
onPress={() => setImage(null)}
icon="retweet"
/>
<Button title="Save" onPress={savePicture} icon="check" />
</View>
) : (
<Button title="Take a picture" onPress={takePicture} icon="camera" />
)}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#000',
padding: 8,
},
controls: {
flex: 0.5,
},
button: {
height: 40,
borderRadius: 6,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
text: {
fontWeight: 'bold',
fontSize: 16,
color: '#E9730F',
marginLeft: 10,
},
camera: {
flex: 5,
borderRadius: 20,
},
topControls: {
flex: 1,
},
});

Unable to push data into an array - React Native

one place I seem to be stuck is on is being able to populate an array of objects, which are used for a FlatList later on.
I get this data from my FireStore – for each document, it will push the objects into ‘const DATA = []’
But when I run the ‘getUsers()’ inside of UseEffect, it only updates ‘DATA’ inside of the method, it doesn’t update the global variable.
Im new to react native, so im probably missing something important within my structure. Thanks for any assistance tho!
I need the format of DATA to look like this example:
My Code:
const MainScreen = () => {
const DATA = [];
const navigation = useNavigation()
const [selectedId, setSelectedId] = useState(null);
const usersCollectionRef = collection(db, "Posts");
const [Data, setData]= useState([]);
LogBox.ignoreLogs(['Setting a timer']);
LogBox.ignoreLogs(['AsyncStorage has been extracted']);
LogBox.ignoreAllLogs(true);
useEffect(() => {
getUsers();
console.log(DATA);
}, []);
const getUsers = async () => {
const data = await getDocs(usersCollectionRef);
data.forEach(doc =>{
const dataG = (doc.id, "=>", doc.data());
DATA.push({
id: doc.id,
title: dataG.status +" "+ dataG.Type,
status: dataG.status,
location: dataG.Location,
type: dataG.Type,
injured: dataG.Injured,
collar: dataG.Collar,
color: dataG.Colour,
username: dataG.username,
description: dataG.Description,
imagesrc: dataG.picture });
})
};
const Item = ({ item, onPress, backgroundColor, textColor }) => (
<View style={styles.ContentBox}>
<TouchableOpacity onPress={onPress} style={[styles.item, backgroundColor]}>
<Text style={[styles.title, textColor]}>{item.title}</Text>
<Text style={styles.ContentText}>By: {item.username}</Text>
</TouchableOpacity>
<Image source = {{uri: item.imagesrc}}
style = {{ width: 200, height: 200, alignSelf:'center' }}/>
<Text style={styles.ContentText}>Animal: {item.type}</Text>
<Text style={styles.ContentText}>Location: {item.location}</Text>
<Text style={styles.ContentText}>Injured: {item.injured}</Text>
<Text style={styles.ContentText}>Colour: {item.color}</Text>
<Text style={styles.ContentText}>Has a Collar: {item.collar}</Text>
<Text style={styles.ContentText}>Description: {item.description}</Text>
</View>
);
const renderItem = ({ item }) => {
const backgroundColor = item.status === "lost" ? '#b80909' : '#098231';
const color = item.id === selectedId ? 'white' : 'white';
return (
<Item
item={item}
onPress={() => setSelectedId(item.id)}
backgroundColor={{ backgroundColor }}
textColor={{ color }}
/>
);
};
const PostScreen = () =>{
navigation.navigate('PostScreen');
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.MainFeed}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
extraData={selectedId}
/>
</View>
)
instead of pushing data in a variable and then updating the state, you can do it like this directly -
setData([...Data,{
id: doc.id,
title: dataG.status +" "+ dataG.Type,
status: dataG.status,
location: dataG.Location,
type: dataG.Type,
injured: dataG.Injured,
collar: dataG.Collar,
color: dataG.Colour,
username: dataG.username,
description: dataG.Description,
imagesrc: dataG.picture
}])
The answer is that after doing DATA.push(...), I needed to setData(DATA).

Edit todo from list in React

I don't know why I can't make it work but I just want to edit the todo from the list and save it onBlur (when I press outside the box). I've made this work before but I think I got brain freeze or something. I deleted my attempts so the functions are empty now. Can someone nudge me in the right direction or just fill in the blanks? Thank you
UPDATE: so I want to press the todo that I've added to the list (the textinput) and then EDIT the already present todo, THEN save it to the list again!
const TEST = () => {
const [todo, setTodo] = useState("")
const [todoList, setTodoList] = useState([])
const onChangeHandler = (text) =>{
setTodo(text)
}
const saveTodo = () =>{
setTodoList([...todoList , {title: todo, key:`${Math.random()}`}])
setTodo("")
}
const onChangeTodo = () =>{
//UPDATE HERE
}
const saveonChangeTodo = () =>{
//SAVE HERE
}
return(
<View style={{flex:1, backgroundColor: "beige", justifyContent: "center", alignItems: "center", paddingTop: 300,}}>
<TextInput
placeholder="Write todo here"
style={{backgroundColor:"white", padding: 20, width: 300,}}
value={todo}
onChangeText={text=>onChangeHandler(text)}
onBlur={saveTodo}
/>
<FlatList
data={todoList}
renderItem={({item}) => {
return(<TextInput style={{borderColor: "black", borderWidth: 2, width: 200, padding: 20, margin: 10}}
value={item.title}
onChangeText={text=>onChangeTodo(text, item)}
onBlur={saveonChangeTodo}
/>
)
}}/>
Change your onChangeTodo as below. No need to have saveonChangeTodo as it is already supported by onChangeTodo.
const onChangeTodo = (text, item) => {
setTodoList((todos) =>
todos.map((todo) =>
todo.key === item.key ? { ...todo, title: text } : todo
)
);
};
Code Sandbox

React Native how can I get last value of stateful array which has been updated by a spread operator?

I am updating a list (kind of like a todo list) and trying to persist it to AsyncStorage but the latest item added to array is always missing. Why?
Here is the offending function (shortened for clarification):
// At beginning of component
let [itemsArray, updateItemsArray] = useState([])
const addItem = async (item) => {
const currentItem = {
id: uuid(), // <-- temporary way of getting key for now
name: item.name
}
// Use spread operator to update stateful array for screen listing
// The listing on the screen updates perfectly with the 'new item' in place at the bottom
of a list
updateJobsArray(prevItems => [...prevItems, currentJob])
// Now, stringify the items array in preparation for saving to AsyncStorage
updateItemsArray(prevItems => [...prevItems, currentItem])
try {
const jsonValue = JSON.stringify(itemsArray)
await AsyncStorage.setItem('items', jsonValue)
} catch (e) {
Alert.alert('Error', 'Something went horribly, irrevocably... wrong')
}
}
When I console.log AsyncStorage.getItem('items'), the last item added is always missing from the resultant list of items. The items list is always missing the last added item. I think that the problem lies in the way the spread operator updates the stateful 'itemsArray'. It's like as if the state update is async and the write to AsyncStorage happens before the update is finished, but I can't find out why, please help...
I reproduce issue with working example, please test code at https://snack.expo.dev/#emmbyiringiro/c65dbb
import * as React from 'react';
import { Text, View, StyleSheet,Button,AsyncStorage,Alert,ScrollView } from 'react-native';
import Constants from 'expo-constants';
import faker from 'faker'
// You can import from local files
import AssetExample from './components/AssetExample';
// or any pure javascript modules available in npm
import { Card } from 'react-native-paper';
export default function App() {
let [itemsArray, updateItemsArray] = React.useState([])
let [savedUsers, updateSavedUsers] = React.useState([])
let [asyncOps,updateAsyncOps] = React.useState({saveStatus:"undetermined",retrieveStatus:"undetermined"})
const save = async ()=>{
const newUser ={
id:faker.datatype.uuid()
,
name: faker.name.findName(), // Rowan Nikolaus
email: faker.internet.email(),// Kassandra.Haley#erich.biz,
phone:faker.phone.phoneNumber(),
}
const tempUsers = [...itemsArray,newUser]
const serializeValues = JSON.stringify(itemsArray)
try {
updateAsyncOps({...asyncOps,saveStatus:"pending"})
await AsyncStorage.setItem('users', serializeValues)
await retrieve()
updateItemsArray(tempUsers)
updateAsyncOps({...asyncOps,saveStatus:"succeeded"})
} catch (e) {
updateAsyncOps({...asyncOps,saveStatus:"failed"})
Alert.alert('Error', 'Something went horribly, irrevocably... wrong')
}
}
const retrieve = async () => {
try {
updateAsyncOps({...asyncOps,retrieveStatus:"pending"})
const value = await AsyncStorage.getItem('users');
if (value !== null) {
// We have data!!
console.log(value);
const deSerializeValue = JSON.parse(value)
updateSavedUsers( deSerializeValue)
updateAsyncOps({...asyncOps,retrieveStatus:"suceeded"})
}
} catch (error) {
// Error retrieving data
Alert.alert('Error', 'Something went horribly, irrevocably... wrong')
updateAsyncOps({...asyncOps,retrieveStatus:"failed"})
}
};
return (
<ScrollView style={styles.container}>
<Card>
<View>
{ savedUsers.map(user=>{
return (
<View style={{paddingVertical:5}} key={user.id}>
<Text> { user.name} </Text>
<Text> { user.email} </Text>
<Text> { user.phone} </Text>
</View>
)
})}
</View>
<View style={{padding:10}}>
<Button onPress={save} title ='Add User ' disabled={asyncOps.saveStatus === 'pending'}/>
<View style={{paddingVertical:10}}>
<Button onPress={retrieve} title ='Retrieve Users ' disabled={asyncOps.retrieveStatus === 'pending'}/>
</View>
</View>
</Card>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});

Resources