This is my first time using firestore and I have a react app where I want to display the logged user's username. I try to fetch the username from a firestore DB where I query users using the email of the user.
firebase.js
export const GetUserName = (email)=>{ //get single doc by email
let name = []; //store the name
//this is where the problem begins
db.collection('users').where("Email", "==" ,email)
.get()
.then(snapShot=>{
snapShot.forEach((snap)=>{
name.push(snap.data().userName); //stores only in array temporarily
console.log(name) //shows correct array
});
})
.catch(err =>{
console.log(err);
})
console.log(name); //shows empty array
return name[0]; //array is empty
}
then I try to display the username in another component I have
profilePage.js
import {GetUserName} from '../firebase';
export default function ProfilePage(){
const {user , logout} = useAuth(); //this is access to user.email
return (
<div>
<h1>Hi {getUsername(user.email)} </h1>
<Button onClick={handleLogout} color = "primary" variant = "outlined">Log out</Button>
</div>
);
}
But I only get "hi" without the username
Since the user name is read from Firestore asynchronously by the time your return name[0] runs, the name.push(snap.data().userName); hasn't run yet. You can easily check this by setting breakpoints on these lines and running the code in a debugger.
In situations like this you need to pass the data to the rendering code by putting it in the component's state. Using the useState hook that'd be:
import {GetUserName} from '../firebase';
export default function ProfilePage(){
const {user , logout} = useAuth(); //this is access to user.email
const [userName, setUsername] = useState();
useEffect(() => {
db.collection('users').where("Email", "==" ,user.email)
.get()
.then(snapShot=>{
snapShot.forEach((snap)=>{
setUsername(snap.data().userName);
});
})
.catch(err =>{
console.log(err);
})
});
return (
<div>
<h1>Hi {userName} </h1>
<Button onClick={handleLogout} color = "primary" variant = "outlined">Log out</Button>
</div>
);
}
Related
I can't quite figure out where or how to call the firebase database to show current user's data inside of a profile container.
When a user signs up, it creates a document in firebase collection 'users' that is the same as the user's unique firebase ID.
async function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password).then((cred) => {
return firebase
.firestore()
.collection("users")
.doc(cred.user.uid)
.set({
age: age,
location: location,
bio: bio,
name: name,
email,
})
.then(() => {
console.log("Document Added");
return true;
});
});
}
I need to call some of the data into the profile component below, but I'm not sure how to call it or what syntax to use.
export default function Profile() {
const { currentUser } = useAuth();
const history = useHistory();
return (
<>
<div className="profileCard__cardContainer">
<ProfileCard className="card"
preventSwipe={["up", "down", "left", "right"]}
>
<div><h2>Profile</h2></div>
<div className="email"><strong>email:</strong> {currentUser.email}
</div>
<div className="name">
{currentUser.doc.data(name)}
</div>
</ProfileCard>
<Link to="/update-profile" className="updateProfile__button">
<Button>Update Profile</Button>
</Link>
</div>
</>
);
}
I know it's probably really simple and there's probably a concept that I don't fully understand.
You'll need to load the document from Firestore to get the data from it. Based on the ReactFire documentation that should be with a functional component, that looks something like:
function UserDetails() {
const userRef = useFirestore()
.collection('users')
.doc(currentUser.uid);
const { status, data } = useFirestoreDocData(userRef);
// easily check the loading status
if (status === 'loading') {
return <p>Loading user profile...</p>;
}
return <p>User bios: {data.bio}</p>;
}
I made a search bar that allows the user to search all sports available in one specific city (if sport is not defined) or a specific sport in a specific city (if sport is defined).
City will allways be defined.
I put 2 inputs (city and sport) on my searchbar and I want immediate results (so that there is a request to my API without any button "search" that triggers the request).
So when the user types something on the city input it triggers a request to the API and when he types something on the sport input it retriggers the request but this time with the city and the sport defined.
Both inputs values are store in states (city and sport).
I manage to do something that seems to work, the only problem is that if I types a sport in my input search, it does not update my request to the API. I have to retype the sport in my input a second time so that the request is updated.
I don't know why it does not update the first time I types something in my sport input because I have specified on my useEffect array that it must re render when the sport state changes.
Can someone help me understand this ?
My code :
import React, { useState, useEffect } from "react";
import style from "../styles/pdrs.module.css";
import axios from "axios";
import SearchBar from "../components/SearchBar";
const Pdrs = ({ setSearchCity, searchSport, setSearchSport }) => {
// if request's result is loading
const [isLoading, setIsLoading] = useState(false);
// search result
const [searchresults, setSearchresults] = useState(
"Lancez une recherche avec au moins une ville !"
);
// state for the searchbar request
const [city, setCity] = useState("");
const [sport, setSport] = useState(0);
// get city's id for API's request
const fetchCity = async () => {
setIsLoading(true);
try {
// city search
const cityResponse = await axios.get(
`${baseAPI}/city/name=${searchCity}`
);
const city = cityResponse.data;
setCity(city);
setIsLoading(false);
} catch (error) {
console.log(error.message);
setIsLoading(false);
}
};
//fetching sport id
const fetchSport = async () => {
setIsLoading(true);
try {
const sportResponse = await axios.get(
`${baseAPI}/activity/name=${searchSport}`
);
setSport(sportResponse.data.data[0].macro_activity_id);
setIsLoading(false);
} catch (error) {
console.log(error.message);
}
};
//fetching final request response
const fetchDataRequest = async () => {
try {
setIsLoading(true);
const results = await axios.get(
`${baseAPI}/pdrs?city_id=${city.id}${
sport ? "¯o_activity_id=" + sport : ""
}`
);
// manage search results
if (results.data.nb_results === 1) {
setSearchresults({
data: [results.data.data],
nb_results: 1,
});
setNbResults(1);
setIsLoading(false);
} else {
setSearchresults(results.data);
setNbResults(results.data.nb_results);
setIsLoading(false);
}
} catch (error) {
console.log(error.message);
setSearchresults(
"Sorry, nothing was found... !"
);
}
};
useEffect(() => {
if (searchCity) {
fetchCity();
}
if (searchSport) {
fetchSport();
}
}, [searchCity, searchSport]);
useEffect(() => {
if (searchCity) {
fetchDataRequest();
}
}, [searchCity, searchSport]);
console.log(searchresults);
return <>
<main className={`container ${style.pdrs}`}>
<section className={style.searchbar}>
<SearchBar
searchCity={searchCity}
setSearchCity={setSearchCity}
searchSport={searchSport}
setSearchSport={setSearchSport}
searchInstallation={searchInstallation}
setSearchInstallation={setSearchInstallation}
searchType={searchType}
setSearchType={setSearchType}
setPage={setPage}
/>
</section>
<section className={style.results}>
{isLoading ? (
<div>Loading...</div>
) : typeof searchresults === "string" ? (
<div className={`${style.container} ${style.noResults}`}>
<h2>{searchresults}</h2>
</div>
) : (
<>
<div className={style.container}>
<div className={style.resultsList}>
{searchresults.data.map((pdrs) => {
return (
// some code displaying the result
);
})}
</div>
</div>
</>
)}
</section>
</main>
</>;
};
export default Pdrs;
Since you are having two useEffect and one is setting city and sport you would need to make debounce for making a call for fetching list by itself.
I would suggest that you firstly make changes to your use effect for API call fetchDataRequest:
useEffect(() => {
if (searchCity) {
fetchDataRequest();
}
}, [city, sport]);
You would listen to the actual data from BE, not from input that you fill.
And secondly you can use library useDebounce from here https://www.npmjs.com/package/use-debounce and use useDebounceCallback to delay calling API call after you select sport/city.
I am new to React and trying to set up a basic client-side session. I am getting an error when I am trying to retrieve the username from the local storage. SyntaxError: Unexpected token p in JSON at position 0 Do you have some suggestions on to to fix it?
NavBar.js
const username = JSON.parse(localStorage.getItem('username'));
const NavBar = props => {
if (props.isAuth !== null) {
return <div>
## here I am trying to display the username
<p>Logged In as {{username}}</p>
</div>;
} else {
return <span>Not logged in</span>;
}
};
export default NavBar;
App.js
function App() {
const [loggedIn, setLoggedIn] = useState(localStorage.getItem("token"));
return (
<div>
<Router>
<Navbar isAuth={loggedIn} />
</Router>
<div>
<button
onClick={() => {
if (loggedIn) {
localStorage.removeItem("token", null);
setLoggedIn(null);
} else {
localStorage.setItem("token", true);
localStorage.setItem("username", "pierre-alex");
setLoggedIn(true);
}
}}
value="toggle"
>
Toggle
</button>
</div>
</div>
);
}
export default App;
thank you
If the value of username is an object then we need to stringify it while storing it in localStorage. We can then parse it to get the data.
localStorage.setItem('username',JSON.stringify({name:'Jonh Doe'}));
const username = JSON.parse(localStorage.getItem('username');
If the value stored in localStorage is not an object then we don't need to parse it. We can access it like below.
const username = localStorage.getItem('username')
In my app I have profile section with a form. When the component mounts I want to fetch user data from firebase, and display it in the form, with the current values of the user profile. Either using the "value" prop or the "placeholder" prop.
When the user makes changes in the form inputs and submit the changes, I want the database to update and the form to update with the new data.
Currently I can make the database value appear in the form input field, or I can make the form input field empty, but update the database. But not both.
The following code makes the database data render in the form input, but it cant be changed.
I know it could be something with the second useEffect() and the getUserData() function, that I cant seem to figure out.
const UserEdit = (props) => {
const [currentUser, setCurrentUser] = useState('');
const [forening, setForening] = useState('');
useEffect(() => {
firebase_app.auth().onAuthStateChanged(setCurrentUser);
}, [])
const getUserData = async () => {
await dbRef.ref('/' + currentUser.uid + '/profil/' ).once('value', snapshot => {
const value = snapshot.val();
setForening(value)
})
}
useEffect(() => {
getUserData()
},[] )
const handleInput = (event) => {
setForening(event.target.value)
}
const updateUserData = () => {
dbRef.ref('/' + currentUser.uid + '/profil/' ).set({foreningsnavn: forening}, function(error) {
if(error) {
console.log("update failed")
} else {
alert(forening)
}
})
}
const handleClick = () => {
updateUserData()
}
return (
<>
<div className="card-body">
<div className="row">
<div className="col-md-5">
<div className="form-group">
<label className="form-label">{Forening}</label>
<input className="form-control" type="text" value={forening} onChange={handleInput}/>
</div>
</div>
</div>
</div>
</>
)
}
Your second useEffect will run only one time because the second argument array [] of dependencies is empty:
useEffect(() => {
getUserData()
},[] )
You can add foreign dependency to make useEffect run with input change
useEffect(() => {
getUserData()
},[foreign] )
or you can use polling to sync database state
So i've basically got 2 components on my page.
First is the search component where the users need to type their username and second one where their stats get displayed
and here is my API request call in App.js
useEffect(()=>{
const fetchStats = async ()=> {
const result = await axios.get(`https://cors-anywhere.herokuapp.com/https://public-api.tracker.gg/v2/csgo/standard/profile/steam/${username}`,
{
headers: {
'TRN-Api-Key' : '***************************',
}
}
)
if(username !== null){
console.log(result.data)
setStats(result.data)
}
}
fetchStats()
},[username])
and this is the search component
const Search = ({setInputText, setUsername, inputText, username}) => {
const inputHandler = (e)=> {
setInputText(e.target.value)
}
const searchHandler = (e)=> {
e.preventDefault()
setUsername(inputText)
}
return (
<div>
<form>
<input value={inputText} onChange={inputHandler} type="text"/>
<button onClick={searchHandler}>Search</button>
</form>
</div>
)
}
What i'm having an issue with is when i click a button in the username component the value(username) from the form gets stored in the 'username' state in App.js. Now i'm using this code in the stats component.
const Stats = ({stats}) => {
return (
<div>
<h1>{stats.data.platformInfo.platformUserHandle}</h1>
</div>
)
}
export default Stats
Here stats.data.platformInfo.platformUserHandle doesn't exist when the app starts so it gives me a LOT of errors. How do i keep the app from crashing till the user has input something and data can be sent to the stats component?