How to retrieve user data from fire store - reactjs

I need to fetch the user data and display it. I am getting an error now that says
TypeError: this.unsubscribe is not a function
and when I initialise it as a normal variable like const db, then I get another error
Function CollectionReference.doc() requires its first argument to be of type non-empty string
import React from "react";
import { auth, firestore } from "../../firebase/firebase.utils";
export default class UserPage extends React.Component {
state = {
user: {}
};
unsubscribe = null;
componentDidMount() {
const user = auth.currentUser;
this.unsubscribe = firestore
.collection("users")
.doc(user)
.onSnapshot(doc => {
this.setState = {
user: doc.data()
};
});
}
componentWillMount() {
this.unsubscribe();
}
render() {
return (
<div>
<h1>{this.state.user.name}</h1>
</div>
);
}
}

when I insinalise it as a normal variable like const db
Not quite sure what you mean by this, but if you're getting an error about the type of unsubscribe, I suggest using console.log right before you call it to view its value.
Bear in mind that componentWillMount happens in the lifecycle before componentDidMount (hence the names will and did). I suspect that's one of your problems: you try to call unsubscribe before setting the value.
With regard to your other error about the doc call, it's likely referring to:
...
.collection("users")
.doc(user) <-- this line
.onSnapshot(doc => {
this.setState = {
us
...
As the error output states, that user variable (the first argument of doc) must be a string, and it can't be an empty string.
I don't see user anywhere in your code, so I expect that it's currently the value undefined. You could access this.state.user here, but I'd strongly advise against it since you subsequently set that state in the call (probably cause an infinite loop).
What is your end goal? What have you tried to resolve these two issues? Maybe adding that to your question would help us assist you better.

Related

React hook for logging in anonymously if no other login method

I want to have my users log in automatically anonymously. That's not too difficult to do. However I don't want anonymous logins to override their account logins. That's where I am running into trouble. I can't seem to find the way to do this.
Here is my hook:
import React, { useEffect, useState } from 'react';
import { singletonHook } from 'react-singleton-hook';
import { useAuth, useUser } from 'reactfire';
function SignInAnonymously() {
const auth = useAuth();
const user = useUser();
useEffect(() => {
user.firstValuePromise.then(() => {
if (!user.data) {
auth.signInAnonymously().then(() => {
console.log('Signed in anonymously');
}).catch((e) => console.log(e));
}
});
}, [user.firstValuePromise, user.data]);
return <></>;
}
export default singletonHook(<></>, SignInAnonymously);
The idea is that we get the first value emitted and compare that to the data object. However, it does not work as I would expect. The value emitted even for someone that was signed in returns null. If I comment the hook the user stays logged into their account. I have spent hours on this trying all the properties of the user so any help is appreciated.
Inside the useUser() method of reactfire, they use firebase.auth().currentUser as the initial value of the observable as seen on this line.
As covered in the Firebase Authentication docs:
Note: currentUser might also be null because the auth object has not finished initializing. If you use an observer to keep track of the user's sign-in status, you don't need to handle this case.
By reactfire setting the initial value to currentUser, you will often incorrectly get the first value as null (which means firstValuePromise will also resolve as null) because Firebase Auth hasn't finished initializing yet.
To suppress this behaviour, we need specify a value for initialData to pass in to useUser. I'd love to be able to use undefined, but thanks to this truthy check, we can't do that. So we need some truthy value that we can ignore such as "loading".
Applying this to your component gives:
/**
* A truthy value to use as the initial value for `user` when
* `reactfire` incorrectly tries to set it to a `null` value
* from a still-initializing `auth.currentUser` value.
*
* #see https://stackoverflow.com/questions/67683276
*/
const USER_LOADING_PLACEHOLDER = "loading";
function SignInAnonymously() {
const auth = useAuth();
const user = useUser({ initialData: USER_LOADING_PLACEHOLDER });
useEffect(() => {
if (user.data !== null)
return; // is still loading and/or already signed in
auth.signInAnonymously()
.then(() => console.log('Signed in anonymously'))
.catch((e) => console.error('Anonymous sign in failed: ', e));
}, [user.data]);
return <></>;
}

React, Typescript - Print fetched data

I am trying to print data from fetched JSON but somehow i am unable to do it.
interface IFetched {
fetchedData: any;
error: any;
}
export default class Testing100 extends React.Component<
ITesting100Props,
IFetched,
{}
> {
constructor(props: ITesting100Props) {
super(props);
this.state = {
error: null,
fetchedData: [],
};
}
public componentDidMount() {
fetch("https://api.randomuser.me/")
.then((res) => res.json())
.then(
(result) => {
this.setState({
fetchedData: result,
});
},
(error) => {
this.setState({
error,
});
}
);
}
public render(): React.ReactElement<ITesting100Props> {
console.log(this.state.fetchedData.results);
return (
<div>
<a>
{this.state.fetchedData.map(function (fetchedDataX) {
return fetchedDataX.results[0].name.first;
})}
</a>
</div>
);
}
}
With console log i am able to print data. But when i change console log from console.log(this.state.fetchedData.results); to console.log(this.state.fetchedData.results[0]); i get nothing. And even that console log is called twice as you can see in console output i dont know why.
But my goal is to print the first name of person into <a> element and I just don't know how. Hope somebody can help me with this. Thanks for your time.
Think about the state of the app before the fetch occurs - the fetchedData array is empty. Then when you fetch, you are converting it into an object, with a results field that is an array.
Your code needs to be able to handle both of these states. Don't try to use or log a field of something that you haven't first verified actually exists, or it will crash.
First step is to clean it up so you directly just update the array in the state -
Your map is not working because fetchedData has an inner results field - try this.setState({fetchedData: result.results});, and then console.log(this.state.fetchedData).
Also you might want to add some guards to the top of your render so that things don't crash when the fetchedData is empty or errored:
if (this.state.fetchedData === []) return <p>"Nothing Loaded"</p>;
if (this.state.error !== null) return <p>{this.state.error.message}</p>;
As for the double output to the console, that is because the render method get run first when the component is mounted, and you see the output where the fetchedData is empty, and then componentDidMount runs (which fetches the data and updates the state) and then React re-renders with the new state, causing the second console log.
Your console log that tries to access the .result[0] fails because it doesn't exist for this first pre-fetch state. Check (with an if) it is there before logging, or log the whole state obj.

Cannot setState() with Firebase database listener

I'm trying to build a mini-game with rooms. When I try to retrieve room information from my firebase realtime database, I used database.ref().on() function to listen on any data changes and setting my state according to the change. Here is what the code looks like:
export default class Room extends Component {
state = {
room: null,
username: "sdff",
};
componentWillMount() {
const roomId = this.props.location.pathname;
app()
.database()
.ref(`/rooms${roomId}`)
.on("value", (snapshot) => {
const data = snapshot.val();
console.log(data);
this.setState({ room: data });
this.setState({ room: "hello" });
});
}
When I do console.log(data), I actually do see that data contains all information that I want. However, this.setState is not working, which causes the page failing to render since lots of the information from state is used in render(). Even if I set room to "hello", as shown in the last statement of the code snippet, it still fails to set state. I'm wondering if it's because render() is called before the data is successfully retrieved? If so, how can I fix this? Thanks in advance.
I think you need componentDidMount() instead.
As React official docs say:
This method is a good place to set up any subscriptions
Also, don't forget to end the subscription on componentWillUnmount()

setState is not updating in my other method

I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )

What is a best way to get value from AsyncStorage before whole app load

I have App.js file and it is root of my application (both ios and android reference to it).
I have a value that I keep in AsyncStorage that I need before app.js render method is called.
Problem is that as it is async it late and I can't get that value.
class App extends React.Component {
constructor(props) {
super(props);
this.init()
}
async init() {
try {
const value = await AsyncStorage.getItem('#myPoorValue:key');
if (value !== null){
...
}
} catch (error) {}
}
}
...
render (...
I hope that I explained good what is my issue here.
I know that there is no way to get it synchronous (I would like that) but don't know what to do in this situation.
To explain it a bit better I use I18n and I manually set I18n.locale to some value and other components get default value before I set it manually.
Just to note I also use redux and I pass selected value to it.
try the following:
...
constructor(props) {
super(props)
this state = {
isLoading: true
}
}
async componentDidMount() {
await this.init()
// you might want to do the I18N setup here
this.setState({
isLoading: false
})
}
async init() {
const value = await AsyncStorage.getItem('#myPoorValue:key')
...
}
...
the thing is that init() returns a promise and you need to wait until it gets resolved. That's when await comes to rescue.
you'll also need to set up some loader that will be there on first render, and toggle the state to replace it with actual markup after the AsyncStorage value has been fetched. I've put it in the code, but you might want to trigger a redux action instead, depending on your setup.

Resources