Fetching data from Firebase and storing in local state is undefined - firebase

I am fetching data from firebase with the result being undefined. When I comment out the elements that use the data, the data gets stored in state, I uncomment the elements and they render without any issues. When I make changes and save the file, it reverts back to being undefined and the elements do not render.
Here is the code I am currently working with :
const router = useRouter();
const { make, model, id } = router.query;
const [singleCar, setSingleCar] = useState([]);
const docRef = doc(db, "car listings", `${id}`);
useEffect(() => {
const fetchDetails = async () => {
await getDoc(docRef).then((doc) => {
const carData = doc.data();
setSingleCar(carData);
});
await onSnapshot(docRef, (doc) => {
const carData = doc.data();
setSingleCar(carData);
});
};
fetchDetails();
}, [singleCar]);
I know I am both fetching a single doc and adding in a real-time listener but I wasn't sure which would be the best action, given the getDoc method only runs once after fetching the document so thought it might be better to have a real-time listener also in place.
Here is what I am rendering, my thinking is that if state is undefined, render the Loader component until the state has changed with the appropriate data.
{singleCar === [] ? (
<Loader />
) : (
<Container maxW="container.xl">
<Box display="flex" justifyContent="space-around">
<Image
w="500px"
src={singleCar.carImages[0].fileURL}
borderRadius="10px"
/>
{/* <CarImageGalleryModal isOpen={isOpen} onClose={onClose} /> */}
<Box
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<SingleCarPrimary
make={singleCar.carDetails.make}
model={singleCar.carDetails.model}
year={singleCar.carDetails.year}
doors={singleCar.carDetails.doors}
engineSize={singleCar.carDetails.engine_size}
fuelType={singleCar.carDetails.fuel_type}
body={singleCar.carDetails.year}
price={singleCar.carPrice}
/>
<ContactSection />
</Box>
</Box>
<CarSummary carDetails={singleCar.carDetails} />
<SingleCarDescription carDescription={singleCar.carDescription} />
</Container>
)}
Here is how the information is being stored in Firebase to give you an idea of what data is being retrieved.
Backend: Firebase version 9
Frontend: Next.js / Chakra UI

The issue should be with the queried document identifier that is not referring to any document, hence the undefined properties result you are getting.
Here down a list of fixes you should apply to your implementation to mitigate such an issue.
You are better using a separate loading state that is will be reset once data is loaded so that it can reflect both loading and empty results:
const router = useRouter();
const { make, model, id } = router.query;
const [singleCar, setSingleCar] = useState({}); // the car should be an object and not an array
const [loading, setLoading] = useState(true);
const docRef = doc(db, "cars", `${id}`); // avoid spaces in document names
useEffect(() => {
const fetchDetails = async () => {
await getDoc(docRef).then((doc) => {
setLoading(false);
if (doc.exists()) {
setSingleCar(doc.data());
}
});
};
// fetch the `car` data
fetchDetails();
// then attach the change listener
await onSnapshot(docRef, (doc) => {
const carData = doc.data();
setSingleCar(carData);
});
}, []); // you should not rerun the effect on state changes as this will keep on reattaching state listener again and again and may fall into endless fetching loops
You component declaration then may be updated as follows (you may still need to update it to reflect empty car state => the data is fetched but there is not car reflecting the queried id):
{loading === true ? (
<Loader />
) : (
<Container maxW="container.xl">
<Box display="flex" justifyContent="space-around">
<Image
w="500px"
src={singleCar.carImages[0].fileURL}
borderRadius="10px"
/>
{/* <CarImageGalleryModal isOpen={isOpen} onClose={onClose} /> */}
<Box
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<SingleCarPrimary
make={singleCar.carDetails.make}
model={singleCar.carDetails.model}
year={singleCar.carDetails.year}
doors={singleCar.carDetails.doors}
engineSize={singleCar.carDetails.engine_size}
fuelType={singleCar.carDetails.fuel_type}
body={singleCar.carDetails.year}
price={singleCar.carPrice}
/>
<ContactSection />
</Box>
</Box>
<CarSummary carDetails={singleCar.carDetails} />
<SingleCarDescription carDescription={singleCar.carDescription} />
</Container>
)}

I was able to fix this using the method of getServerSideProps which I think removes the need for useEffect.

Related

How to prevent duplicate initialState from client response in next-redux-wrapper?

I am using next redux wrapper in App.getInitialProps but in client response I get duplicated initialState are: initialState outside pageProps and inside pageProps, this happens everytime when navigate to another page. How can I fix it?
You can see the image below:
My code below:
function App({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
App.getInitialProps = wrapper.getInitialPageProps((store) => async (context) => {
if (context.req) {
const dataAuth = await fetchAuth(context);
// dispatch action auth to update data initialState
// this only running on the server-side
if (dataAuth) store.dispatch(authSlice.actions.setAuth(dataAuth));
else store.dispatch(authSlice.actions.resetAuth());
}
});

Can't find variable error when using SetState following user auth

I get the error
WARN Possible Unhandled Promise Rejection (id: 20):
ReferenceError: Can't find variable: setUser
I am using Firebase for User Authentication in a React Native app. When the user becomes logged in, I call setUser().
export default function App() {
// Set an initializing state whilst Firebase connects
const [initializing, setInitializing] = useState(true);
const [user, setUser] = useState();
useEffect(() => {
const subscriber = auth.onAuthStateChanged((_user) => {
if (!_user) {
...
}
else {
setUser(_user);
if (initializing) setInitializing(false);
}
});
return subscriber; // unsubscribe on unmount
},[]);
}
I use user to determine which Stack.Screen loads.
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Group>
{ user && <Stack.Screen name="CoreTabs" component={CoreTabs} options={{ headerShown: false, }} /> }
<Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false, }}/>
...
</Stack.Group>
<Stack.Group screenOptions={{ presentation: 'modal' }}>
...
</Stack.Group>
</Stack.Navigator>
</NavigationContainer>
);
The error surfaces when I change between screens. I am guessing the auth changes on other screens could be triggering the useEffect, but I don't understand why setUser is being read as a variable.

Create and add multiple map(objects) fields in document in React Native Firebase Firestore

I am new to React Native, and I am trying to build an app.
The problem I am facing now, is creating map(objects) in documents of firebase firestore using firebase.firestore().... .add function.
I've managed to get simple string inputs to save(name, title and age - what user types in text input are successfully saved) but I didn't find a way to make it save to objects(named maps in firebase firestore) & also let user add infinite teammates input in frontend.
My goal is for the user to be able to input this:
Age, Name, Title(already solved) and Teammates
So the document in firebase should have (Age:Str, Name:Str, Title:Str, Teammates:NestedObject(map)>Teammate1(Object)>Name,Title
-- I attached pictures of UI, and database currentDoc, and neededDoc for better clarification
How should I go about it? Would love if you could help me. Thanks
Pics: UserUI: https://imgur.com/a/7CsPeQQ
actual Firestore Doc: https://imgur.com/a/0Up1i8S
needed Firestore Doc: https://imgur.com/a/OpzGl4x
EDIT /// SOLVED Firebase Map/Object uploading issue. However I still couldn't find a way to add n inputs to object.
import React, {useState} from 'react';
import { View, Image, TextInput, Button } from 'react-native';
import firebase from 'firebase';
import { user } from '../redux/reducers/user';
require("firebase/firestore") ;
require("firebase/firebase-storage") ;
export default function Save(props, {navigation}) {
const [age, setAge] = useState("");
const [name, setName] = useState("");
const [title, setTitle] = useState("");
// const [teammates, setTeammates] = useState("");
const savePostData = () => {
firebase.firestore()
.collection('posts')
.doc(firebase.auth().currentUser.uid)
.collection("userPosts")
.add({
teammates:{},
age,
name,
title,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
}).then((function () {
props.navigation.popToTop()
}))
}
return (
<View style={{flex:1}}>
<TextInput
placeholder = "Age"
onChangeText = {(age) => setAge(age) }
/>
<TextInput
placeholder = "Name"
onChangeText = {(name) => setName(name) }
/>
<TextInput
placeholder = "Title"
onChangeText = {(title) => setTitle(title) }
/>
<Button title="Post" onPress = {() => savePostData()} />
</View>
)
}

Get URL to my media player from Firestore by using redux

I try to add my URL from firebase to my mediaplayer by using redux. If I print my URL in a text component in my flatlist, the URL display. But when I try to set the url to my media player it doesn't work. If I just copy the url from firebase it works. So what schould I do if I want my URL adress from Firebase by using redux?
async function playSound() {
console.log('Loading Sound');
const { sound } = await Audio.Sound.createAsync(
{uri: 'Url copied from firebase storage works fine here.'}
//But if I try to get URL from my redux I doesn't work.
{uri: userSongs.downloadURL}
);
When I set the url in my flatlist like this it works fine.
<FlatList
numColumns={3}
horizontal={false}
data={userSongs}
renderItem={({ item }) => (
<View>
<Text>{item.downloadURL}</Text>
</View>
)}
/>
this is my action.js in my redux
export function fetchUserSongs() {
return ((dispatch) => {
firebase.firestore()
.collection("users")
.doc(firebase.auth().currentUser.uid)
.collection("usersSong")
.orderBy("creation", "asc")
.get()
.then((snapshot) => {
let posts = snapshot.docs.map(doc => {
const data = doc.data();
const id = doc.id;
return { id, ...data }
})
dispatch({ type: USERS_SONGS_STATE_CHANGE, posts })
console.log(posts);
})
})
}

Firebase Authentication - How To Deal With Delay

I have set-up my Firebase project as per the video from David East, as below with this in my app.js file. I have removed my config parameters.
#topName refers to an element on the page that displays the authenticated user's username. Unfortunately what happens is that someone logs in, or is logged in and goes to the page, it initially displays guest and then after some time it switches to the username of that user. This is quick (<500ms) but causes the page to render twice which is confusing.
How can I avoid this, do I need to store something in local storage?
(function() {
//Initialise Firebase
var config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: ""
};
firebase.initializeApp(config);
//Add a realtime listener.
firebase.auth().onAuthStateChanged(firebaseUser => {
if (firebaseUser) {
console.log(firebaseUser);
$('#topName').text(firebaseUser.email);
}
else
{
console.log('not logged in');
$('#topName').text("Guest");
}
});
}());
This is normal, it happens since the data that is being entered is being sent to the Firebase server, then you wait for a response from Firebase to check if this email is authenticated or not. Also internet connection can effect this.
So lots of stuff are happening in the background, to solve this maybe add a loading spinner widget, or try and store the credentials locally.
To solve this you can use localStorage example:
localStorage.setItem("lastname", "userx"); //store data
document.getElementById("result").innerHTML = localStorage.getItem("lastname") //to retrieve
For more info check this: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
Or you can use sessionStorage, more info here: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
i solved this with the next code, just show a loader component while waiting for auth.onAuthStateChanged, the var has three values null, true and false.
const Routes = () => {
const dispatch = useDispatch();
// firebase observer for user auth
useEffect(() => {
let unsubscribeFromAuth = null;
unsubscribeFromAuth = auth.onAuthStateChanged(user => {
if (user) {
dispatch(setCurrentUser(user));
} else {
dispatch(clearCurrentUser());
}
});
return () => unsubscribeFromAuth();
}, [dispatch]);
return (
<Switch>
<Route exact path="/" component={Dashboard} />
<Route path="/signin">
<SignIn />
</Route>
<ProtectedRoute path="/protected">
<Dashboard />
</ProtectedRoute>
<Route path="*">
<NoMatch />
</Route>
</Switch>
);
};
const ProtectedRoute = ({ children, ...rest }) => {
const currentUser = useSelector(state => state.auth.currentUser);
if (currentUser === null) {
// handle the delay in firebase auth info if current user is null show a loader if is false user sign out
// TODO create a loading nice component
return <h1>Loading</h1>;
}
return (
<Route
// eslint-disable-next-line react/jsx-props-no-spreading
{...rest}
render={({ location }) =>
currentUser ? (
children
) : (
<Redirect
to={{
pathname: '/signin',
state: { from: location }
}}
/>
)
}
/>
);
};
export default Routes;

Resources