Undefined Array(but its not) when retrieved from firebase realtime database - reactjs

I am trying to retrieve the data from firebase realtime database and then pass it another function.
But it throws an error saying
TypeError: undefined is not an object (evaluating 'notesList.map')
function NotesList() {
const[notesList, SetNotesList] = useState();
useEffect(() => {
const NoteRef = firebase.database().ref('localnotes-data');
NoteRef.on('value', (snapshot)=> {
const notes = snapshot.val();
const container = [];
for(let id in notes){
container.push({id, ...notes[id] });
}
if (container){
SetNotesList(container);
}
}, []);
})
console.log(notesList);
return (
<View style={styles.list}>
<ScrollView>
<Search />
<Add />
{notesList.map((note) => (<Note text = {text} date = {date}/>))}
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
list: {
marginTop: 0,
marginBottom: 145,
}
})
export default NotesList
This is console.log(notesList)

Because noteList is undefined -> const[notesList, SetNotesList] = useState();
So check that noteList exists to call map when you asynchronous fetch is finished.
You can also initialise you noteList like so -> const[notesList, SetNotesList] = useState([]);
The following code should work tho.
...
return (
<View style={styles.list}>
<ScrollView>
<Search />
<Add />
{notesList && notesList.map((note) => (<Note text = {text} date = {date}/>))}
</ScrollView>
</View>
)
}
...

As an alternative to #Dharmaraj's answer, you can also introduce a "loading" variable like so:
Note: Make sure to take a look at the other changes like some variable names, using DataSnapshot#forEach() to maintain the order from the query, detaching the snapshot listeners, snapshot error-handling and making sure the key property is set in the map() function.
let renderCount = 0; // just for debugging, remove it later
function NotesList() {
const [notesList, setNotesList] = useState();
const notesLoading = notesList === undefined;
useEffect(() => {
const notesQueryRef = firebase.database()
.ref('localnotes-data');
// you can add `orderByChild()`, etc. to the above query
const listener = notesQueryRef.on(
'value',
(snapshot) => {
const notesArray = [];
snapshot.forEach(noteSnapshot =>
const id = noteSnapshot.key;
notesArray.push({
id: noteSnapshot.key,
...noteSnapshot.val()
});
);
setNotesList(notesArray);
},
(error) => {
// TODO: Handle errors better than this
console.error("Failed to get notes: ", error);
}
);
// return cleanup function
return () => {
notesQueryRef.off('value', listener);
};
}, []);
// just for debugging, remove it later
console.log({
renderCount: ++renderCount,
notesLoading,
notesList
});
return (
<View style={styles.list}>
<ScrollView>
<Search />
<Add />
{notesLoading
? <Spin tip="Loading..." key="loading" />
: notesList.map(note => (<Note
text={note.text}
date={note.date}
key={note.key}
/>));
}
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
list: {
marginTop: 0,
marginBottom: 145,
}
})
export default NotesList

I think that's because initial state of notesList is undefined. Try setting that to an empty array.
const [notesList, SetNotesList] = useState([]);
^^
Now notesList is defined and you can run the map() method on it.

Related

How do I access the data from a referenced firestore document?

My data in firestore is structured as shown in the pictures. In my code, I am using a map of the exercises array within my workout document like this:
{workout.exercises.map((exercise) => {
return (
<Text>Want exercise name here</Text>
)
I believe I have to use doc.data() from getDoc(exercise).then((doc) => to get the actual values, but I can't seem to figure out how to actually render the exercise name. It's driving me crazy, any help would be greatly appreciated!
This is the exercise object referenced in the exercises array from the picture above:
EDIT: If the exercises array only has one element, it works properly, but if it has >1, the data seems to keep rendering infinitely which results in flashing and changing text.
EDIT2: Here's my full component code in case that helps:
export function WorkoutDisplayScreen({ navigation }) {
const [workouts, setWorkouts] = React.useState([]);
const workoutsArray = [];
const [isFirstLoad, setIsFirstLoad] = useState(true);
const [exerciseData, setExerciseData] = useState({});
async function getData(exercise) {
await getDoc(exercise).then((doc) => {
setExerciseData(doc.data());
});
}
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsDocs = await getDocs(workoutsCollection);
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
};
useEffect(() => {
console.log('Rendering...');
if (isFirstLoad) {
getWorkouts();
}
setIsFirstLoad(false);
}, []);
return (
<ScreenContainer>
<View style={styles.headerrow}>
<TouchableOpacity
onPress={() => {
navigation.goBack();
}}
>
<AntDesign name="leftcircle" size={24} color="black" style={styles.headericon} />
</TouchableOpacity>
<Text style={styles.header}>My Workouts</Text>
</View>
{workouts &&
workouts.map((workout) => {
return (
<View key={workout.id}>
<View style={styles.workoutnamecontainer}>
<Text style={styles.workoutnametext}>{workout.name}</Text>
</View>
{workout.exercises.map((exercise, index) => {
getData(exercise);
return (
<View style={styles.exercisecontainer} key={index}>
<Text style={styles.exercisetext}>{exerciseData.name}</Text>
<Text style={styles.exercisetext}>{exerciseData.type}</Text>
<Text style={styles.exercisetext}>{exerciseData.weight}</Text>
<Text style={styles.exercisetext}>{exerciseData.reps}</Text>
</View>
);
})}
</View>
);
})}
</ScreenContainer>
);
}
EDIT3:
I can now log the data in the correct format with, but trying to render it on screen is still not working correctly.
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsSnapshot = await getDocs(workoutsCollection);
let workoutsDocs = workoutsSnapshot.docs;
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
workouts.map((workout) => {
workout.exercises.forEach((exercise) => {
getDoc(exercise).then((doc) => {
console.log(workout.name + ':');
console.log(doc.data());
});
});
});
};
I believe you should do all the data-fetching i.e. for all the workouts and every exercise in each workout in the useEffect itself. I have never seen code where data fetching is done inside the JSX part of the function. I am highly skeptical about it.
Also, one thing I noticed while looking through your code is that you are iterating over the snapshot instead of docs in the the snapshot inside your getWorkouts() function. It should be as follows:
const getWorkouts = async () => {
console.log('Firebase READ & WRITE');
const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
const workoutsSnapshot = await getDocs(workoutsCollection);
workoutsDocs = workoutsSnapshot.docs; // This line must be added to your code
workoutsDocs.forEach((workout) => {
workoutsArray.push(workout.data());
});
setWorkouts(workoutsArray);
};

Real-Time graph not showing in React

I want to plot my data in React-Native. I am receiving data from BLE. But I can't see the real time graph on the screen. What could be the reason? My brain stopped, so I couldn't figure out what the problem was.
Note : My data is,
hr
This is BLE characteristic value.
This is my component.js,
const width = Dimensions.get('window').width;
const height = Math.floor((Dimensions.get('window').height - 150) / 3);
let counter = 0;
const slotsPerWidth = 100;
const initialState = {
flow: hr,
isLoading: false,
};
class ChartScreen extends Component {
state = {
chartData: { ...initialState },
};
static getDerivedStateFromProps(state) {
counter++;
return {
chartData: {
flow: [hr],
},
};
}
render() {
const { flow} = this.state.chartData;
return this.state.isLoading
? <ScreenContainer>
<View style={styles.main}>
<View>
<ActivityIndicator></ActivityIndicator>
</View>
<Chart
key="flow"
data={flow}
maxValue={1900}
minValue={1750}
slotsPerWidth={slotsPerWidth}
width={width}
height={height}
marginBottom={20}
lineColor="rgba(95, 92, 1, 1)"
lineThickness={2}
chartBackground="#17204d"
horizontalGridLinesCount={5}
gridColor="rgba(65, 95, 93, .4)"
gridThickness={1}
unit="ml"
axisTooClose={10}
labelsColor="rgba(255, 255, 255, 0.8)"
labelsFontSize={12}
marginLeft={60}
labelsMarginLeft={15}
/>
</View>
</ScreenContainer>:
null
}
}
const styles = StyleSheet.create({
main: {
flex: 1,
},
});
export default ChartScreen;
This is my device screen.tsx,
const DeviceScreen = ({
route,
navigation,
}: StackScreenProps<RootStackParamList, 'Device'>) => {
// get the device object which was given through navigation params
const { device } = route.params;
const [isConnected, setIsConnected] = useState(false);
const [services, setServices] = useState<Service[]>([]);
// handle the device disconnection
const disconnectDevice = useCallback(async () => {
navigation.goBack();
const isDeviceConnected = await device.isConnected();
if (isDeviceConnected) {
await device.cancelConnection();
navigation.navigate('Home');
}
}, [device, navigation]);
useEffect(() => {
const getDeviceInformations = async () => {
// connect to the device
const connectedDevice = await device.connect();
setIsConnected(true);
// discover all device services and characteristics
const allServicesAndCharacteristics = await connectedDevice.discoverAllServicesAndCharacteristics();
// get the services only
const discoveredServices = await allServicesAndCharacteristics.services();
setServices(discoveredServices);
PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Permission Localisation Bluetooth',
message: 'Requirement for Bluetooth',
buttonNeutral: 'Later',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
}
);
};
getDeviceInformations();
device.onDisconnected(() => {
navigation.navigate('Home');
});
// give a callback to the useEffect to disconnect the device when we will leave the device screen
return () => {
disconnectDevice();
};
}, [device, disconnectDevice, navigation]);
return (
<ScrollView contentContainerStyle={styles.container}>
<TouchableOpacity style={styles.button} onPress={disconnectDevice}>
<Text style={{fontFamily:"SairaExtraCondensedThin",textAlign:"center",fontSize:15,color:"white"}}>Antrenmanı Sonlandır</Text>
</TouchableOpacity>
<View>
<View style={styles.header} >
<Text>{`Name : ${device.name}`}</Text>
<Text>{`Is connected : ${isConnected}`}</Text>
</View>
{services &&
services.map((service) => <ServiceCard service={service} />)}
</View>
<View>
<ChartScreen chartdata />
</View>
</ScrollView>
);
};
Well, at least, in component.js the isLoading state is never set to true and in screen.tsx you are non passing any data since <ChartScreen chartdata /> means <ChartScreen chartdata={true} />, so it is a boolean property. Then, in getDerivedStateFromProps you are increasing counter that is not part of the state, so, by itself, it will not trigger a new render. Check also that the value of hr is set in the proper place, looks strange to me to see it not as a part of the state too, or as a property. It needs some investigation. Also, the useEffect dependencies are objects so check this out, but consider to refactor the useEffect part, react useEffect comparing objects
Note
Seems to me that there are some misconceptions about React here cousing most of the issues.
This may not be the actual and full answer but I decided to provide my input as a starting point for further investigation and impovements.

Change color of a component inside a function dinamically

I have the next component:
const DetailsInformation = () => {
const [details, setDetails] = useState(null)
useEffect(()=>{
getDetails()
}, [])
const getDetails = async () => {
await getData().then((details)=>{
details.forEach((detail)=>{
detail.posibles_valores.forEach((valor)=>{
valor.selected = false
})
})
setDetails(filteredDetails)
})
}
const getIconColor = (value) => {
if(!value) return 'grey';
return Colors.primary
}
const updateDetail = (nombre_detalle, detalle) => {
let detailToUpdateIndex = details.findIndex(det => det.nombre.toLowerCase() == detalle.toLowerCase())
let innerDetailToUpdateIndex = details[detailToUpdateIndex].posibles_valores.findIndex(valor => valor.valor_detalle.toLowerCase() == nombre_detalle.toLowerCase())
let updatedDetails = details;
updatedDetails[detailToUpdateIndex].posibles_valores[innerDetailToUpdateIndex].selected = !updatedDetails[detailToUpdateIndex].posibles_valores[innerDetailToUpdateIndex].selected
updatedDetails[detailToUpdateIndex].posibles_valores[innerDetailToUpdateIndex].color = getIconColor(!updatedDetails[detailToUpdateIndex].posibles_valores[innerDetailToUpdateIndex].selected)
setDetails(updatedDetails)
}
const generateDetailValues = (detail) => {
let detailSelected = details.find(det => det.nombre.toLowerCase() == detail.toLowerCase())
return (<View>
{detailSelected.posibles_valores.map((detalle, index)=>(
<TouchableWithoutFeedback key={detalle.valor_detalle} onPress={()=>{updateDetail(detalle.valor_detalle, detail, index)}}>
<View style={{justifyContent:'space-between', marginHorizontal:30, alignItems:'center', flex:1, flexDirection:'row'}}>
<Text bold style={{fontSize:18, marginVertical:2, marginHorizontal:8, textAlign:'center'}}>{detalle.valor_detalle}</Text>
<Icon type='AntDesign' name='checkcircle' style={{fontSize: 20, color:detalle.color}}/>
</View>
</TouchableWithoutFeedback>
))}
</View>)
}
return (
<View>
<Text>List One</Text>
<View>
{ details && generateDetailValues('list_one', details)}
</View>
<Text>List two</Text>
<View>
{details && generateDetailValues('list_two', details)}
</View>
</View>
)
}
In summary, I make an API call and after, I render the component returned by "generateDetailValues", this component have a "onPress" function which changes the color of the icon inside the component.
The problem is when I'm press that touchable, the values of "details" change, but the color doesn't , that's because the "generateDetailValues" doesn't executed.
Any idea how I can make that function execute (in the render method) when "details" change?
Thanks in advance.
getIconColor always returns Colors.primary no matter what value you pass into the function, I guess this is why the color never changes.
I don't know how you your Color object works but it seems like it should depend on value in some way, e.g:
const getIconColor = (value) => {
if(!value) return 'grey';
return Colors.value.primary
}

How to render item when data is updated react native

Hi guys I am currently facing a problem where in I need to update the data inside of a pop up
here's my code
useEffect(() => {
return ref.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const {
accepted,id,name,rating,services,userDestinationLat,userDestinationLng,userOriginLat,userOriginLng
} = doc.data();
list.push({ accepted, id, name, rating, services, userDestinationLat, userDestinationLng, userOriginLat, userOriginLng });
});
setUserBookingData(list);
});
}, []);
useEffect(() => {
// use this just to get userbookingData
console.log(userBookingData);
},[userBookingData]);
const [newOrder, setNewOrder] = useState({
id: '1',
service: userBookingData.services,
originLatitude : originalPos.latitude,
originLongitude: originalPos.longitude,
destinationLatitude: 45.24953,
destinationLongitude: -76.360733,
user:{
rating: userBookingData.rating,
name: userBookingData.name,
}
});
now here on my return view
return(
{ !userBookingData.length ? (<NewOrderPopUp
newOrder={newOrder}
onDecline={onDecline}
duration={2}
distance={0.5}
onAccept={() => onAccept(newOrder)}
/>) : (
<View></View>
)
}
);
here's the popup component it's in another page
const NewOrderPopUp = ({newOrder, onAccept, onDecline, duration, distance}) => {
return (
<View style={styles.root}>
<Pressable onPress={onDecline} style={styles.declineButton}>
<Text style={styles.declineText}>Decline</Text>
</Pressable>
<Pressable onPress={onAccept} style={styles.popupContainer}>
<View style = {styles.row}>
<Text style = {styles.service}>{newOrder.service}</Text>
{/* <Image source={{}}/> */}
<View style={styles.userBg}>
<FontAwesome name={"user"} color={"white"} size={35}/>
</View>
<Text style = {styles.service}>
<AntDesign name={"star"} size={16}/>
{newOrder.user.rating}
</Text>
</View>
<Text style = {styles.minutes}>{duration} mins</Text>
<Text style = {styles.distance}>{distance} KM</Text>
</Pressable>
</View>
);
};
it won't update the data / show the pop up wen there's a data
The expectation output is it should be able to pop up the NewOrderPopUp screen when there's a data.
here's the data from the userBookingData
I need to display it here
The fact that you are filling userBookingData in first useEffect does not allow you to write an useState getting value from userBookingData itself.
React doesn't work like that.
Not only but if I understand correctly, userBookingData is an array so write userBookingData.services returns null.
Lets suppose that you want to have in newOrder the first element that comes from userBookingData. In this case, you should write something like:
const [newOrder, setNewOrder] = useState({});
useEffect(() => {
return ref.onSnapshot(querySnapshot => {
const list = [];
querySnapshot.forEach(doc => {
const {
accepted,id,name,rating,services,userDestinationLat,userDestinationLng,userOriginLat,userOriginLng
} = doc.data();
list.push({ accepted, id, name, rating, services, userDestinationLat, userDestinationLng, userOriginLat, userOriginLng });
});
setUserBookingData(list);
// here set newOrder
let resultObj = {};
resultObj.id = 1;
resultObj.service = list[0].services;
...
setNewOrder(resultObj);
});
}, []);

Why does AsyncStorage save an empty array with the first click and then save the entered value only with the second button click in react native

I try to save the tasks in my ToDo app with AsyncStorage so that they can be retrieved after an app restart.
So far I have managed to save the tasks. However, an empty array is always saved in the first run. If I want to create a new task, it only saves it the second time I click the button. Logical if the whole thing runs asynchronously. I just can't figure out where my fault is. I would be very happy to receive help and tips.
Here you can see the empty array when creating the first task:
Reactotron Empty Array
And here you can see the first value get's saved after i created the second task:
Reactotron AsyncStorage after second click
First Part:
if (__DEV__) {
import('./ReactotronConfig').then(() => console.log('Reactotron Configured'));
}
import Reactotron, { asyncStorage } from 'reactotron-react-native';
import React, { useState, useEffect } from 'react';
import {
Keyboard,
KeyboardAvoidingView,
Platform,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
ScrollView,
Image,
SafeAreaView,
} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
import Task from './components/Task';
export default function App() {
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('TASKS');
const jsonValue2 = JSON.parse(jsonValue);
if (jsonValue2 !== null) {
setTaskItems(jsonValue2);
}
} catch (e) {
alert(e);
}
};
const storeData = async () => {
await AsyncStorage.clear();
try {
const jsonValue = await AsyncStorage.setItem(
'TASKS',
JSON.stringify(taskItems)
);
return jsonValue;
Reactotron.log(jsonValue);
} catch (e) {
alert(e);
}
};
useEffect(() => {
getData();
}, []);
const handleAddTask = () => {
storeData();
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
setTask(null);
};
const completeTask = (index) => {
let itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
setTaskItems(itemsCopy);
};
const bearyDustLogo = require('./assets/bearydust-logo-bear-with-text.png');
Second Part:
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.scrollView}>
{/* Aufgaben für heute */}
<View style={styles.tasksWrapper}>
<View style={styles.headerWrapper}>
<Text style={styles.sectionTitle}>StandUp Aufgaben</Text>
<Image style={styles.tinyLogo} source={bearyDustLogo}></Image>
</View>
<View style={styles.items}>
{/* Aufgabenbereich */}
{taskItems.map((item, index) => {
return (
<TouchableOpacity
key={index}
onPress={() => completeTask(index)}
>
<Task text={item} />
</TouchableOpacity>
);
})}
</View>
</View>
</ScrollView>
{/* Aufgabe erstellen */}
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={styles.writeTaskWrapper}
>
<TextInput
style={styles.input}
placeholder={'Ey! Lass was machen'}
value={task}
onChangeText={(text) => setTask(text)}
/>
<TouchableOpacity
onPress={() => {
handleAddTask();
}}
>
<View style={styles.addWrapper}>
<Text style={styles.addText}>+</Text>
</View>
</TouchableOpacity>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
Looking at the code, it's first saving and then it's updating the array, so you will always be one step behind on your storage:
const handleAddTask = () => {
storeData(); // here you are handling the save
Keyboard.dismiss();
setTaskItems([...taskItems, task]); // but you update the values only here
setTask(null);
};
To keep your code simple I would suggest you to save each time you have an update on your taskItems but keep in mind you don't need to update the first time you load from storage, so something like this should work:
export default function App() {
const [task, setTask] = useState();
const [taskItems, setTaskItems] = useState([]);
const [loading, setLoading] = useState(true);
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('TASKS');
const jsonValue2 = JSON.parse(jsonValue);
if (jsonValue2 !== null) {
setTaskItems(jsonValue2);
}
} catch (e) {
alert(e);
} finally {
setLoading(false)
}
};
const storeData = async () => {
// commenting this line as you don't need to clean the storage each time you write something on it, as you'll just override the existing key 'TASKS'
// await AsyncStorage.clear();
// here you validate if the data was loaded already before starting watching for changes on the list
if (!loading) {
try {
const jsonValue = await AsyncStorage.setItem(
'TASKS',
JSON.stringify(taskItems)
);
return jsonValue;
Reactotron.log(jsonValue);
} catch (e) {
alert(e);
}
}
}
useEffect(() => {
getData();
}, []);
useEffect(() => {
storeData()
}, [taskItems])
const handleAddTask = () => {
// won't need this line anymore as now everything you update your list it will be saved.
// storeData();
Keyboard.dismiss();
setTaskItems([...taskItems, task]);
setTask(null);
};
const completeTask = (index) => {
let itemsCopy = [...taskItems];
itemsCopy.splice(index, 1);
// this will trigger a save as well
setTaskItems(itemsCopy);
};
const bearyDustLogo = require('./assets/bearydust-logo-bear-with-text.png');
This way you will always save any updates on the task list and will prevent to save then as empty when first rendering your component.
Success on your project.

Resources