Local Storage values not getting loaded in React App - reactjs

I am new to React and following one of the video series available on Udemy (React for the rest of us by Brad Schiff). The author has provided a backend system for user login etc.
When the user logs in, the backend server sends 3 values in response to a successful login: the login token, the user name and the avatar url (a dummy image from gravatar.com). These values are then stored in local storage from where they can be displayed on the UI:
const response = await axios.post("http://localhost:8080/login", {username, password});
if (response.data) {
// if response contains token, set login flag to true
props.loginFlagHandler(true);
// save user token, username, avatar in localstorage
localStorage.setItem("username", response.data.username);
localStorage.setItem("loginToken", response.data.token);
localStorage.setItem("avatar", response.data.avatar); }
Now, when I log in and the UI updates, the values received from server are successfully stored in local storage of my browser, but they are not getting reflected on the UI. The avatar image doesn't load and the Welcome message that should display the username doesn't work as expected (see the 1st image below). The Welcome message should read "Hello <username>, your feed is empty"
However, once I hit refresh, both the Avatar Image and the User name get displayed as they should, on their relevant place. (refer 2nd image).
In both the cases, I am referring to the localStorage variable as below:
<img className="small-header-avatar" src={localStorage.getItem("avatar")} />
And:
<h2 className="text-center">
Hello <strong>{localStorage.getItem("username")}</strong>, your feed is empty.
</h2>
I think this should be a noob thing that I'm missing here. Nevertheless, thanks in advance:

In React, when a component mounts
states are load
UI prints with the initial states
Lifecycle hooks run (e.g. useEffect) and update states
DOM re-prints with updated states
if you don't use state in your component then you should follow the above workflow to update your UI. Though I don't know what your props.loginFlagHandler(true) does, I am giving an example below.
const [isDataStored, setIsDataStored] = useState(false)
useEffect(() => {
const fetchData = async () => {
const response = await axios.post("http://localhost:8080/login", {username, password});
if (response.data) {
// if response contains token, set login flag to true
props.loginFlagHandler(true);
// save user token, username, avatar in localstorage
localStorage.setItem("username", response.data.username);
localStorage.setItem("loginToken", response.data.token);
localStorage.setItem("avatar", response.data.avatar);
}
setIsDataStored(true)
}
}, [])
you can also try lazy loading your child component.

Well you should use state like this.
const [avatar, setAvatar] = useState("");
const [username, setUsername] = useState("");
useEffect(() => {
setUsername(localStorage.getItem("username"))
setAvatar(localStorage.getItem("avatar"))
})
................
<img className="small-header-avatar" src={avatar} />
And:
<h2 className="text-center">
Hello <strong>{username}</strong>, your feed is empty.
</h2>

Related

How to manage global states with React Query

i have a project that's using Nextjs and Supabase. I was using context API and now i'm trying to replace it for React Query, but i'm having a hard time doing it. First of all, can i replace context completely by React Query?
I created this hook to get the current user
export const getUser = async (): Promise<Profile> => {
const onFetch = await supabase.auth.getUser();
const userId = onFetch.data.user?.id;
let { data, error } = await supabase
.from("profiles")
.select()
.eq("id", userId)
.single();
return data;
};
export const useUser = () => {
return useQuery(["user"], () => getUser());
};
I'm not sure how to trigger it. When i was using context i was getting the user as this. If data, then it would redirect to the HomePage
let { data, error, status } = await supabase
.from("profiles")
.select()
.eq("id", id)
.single();
if (data) {
setUser(data);
return true;
}
Since i was getting the user before redirecting to any page, when i navigated to profile page, the user was already defined. How can i get the user before anything and keep this state? I suppose that once the user is already defined, at the profile component i can call useUser and just use it's data. But it's giving me undefined when i navigate to profile, i suppose that it's fetching again.
const { data, isLoading } = useUser();
But it's giving me undefined when i navigate to profile, i suppose that it's fetching again.
Once data is fetched when you call useUser, it will not be removed anymore (unless it can be garbage collected after it has been unused for some time). So if you do a client side navigation (that is not a full page reload) to another route, and you call useUser there again, you should get data back immediately, potentially with a background refetch, depending on your staleTime setting).
If you're still getting undefined, one likely error is that you are creating your QueryClient inside your app and it thus gets re-created, throwing the previous cache away. You're not showing how you do that so it's hard to say. Maybe have a look at these FAQs: https://tkdodo.eu/blog/react-query-fa-qs#2-the-queryclient-is-not-stable

React Firebase UseEffect Firestore

I've got a user bar component directly adjacent to my navbar, showing avatar and display name (user details saved within a userid-named doc). I followed the firestore documentation for real time updates. Placed inside a use effect hook, which has a dependency array, but is firing repetitively, firing off about 4,000 calls a minute per logged in user).
I really just want the snapshot to update when a user updates their display name or avatar. (found on a different form component)
I console logged every useEffect hook in my project to narrow it down to this component. Is there something about the dependency array causing this? Is there a better way to write this? Thanks!
export default function UserBar() {
const { user } = useAuthContext()
const [me, setMe] = useState([])
useEffect(() => {
//user real-time subscription for avatar and displayname
const auth = getAuth()
const fireUser = auth.currentUser
const unsub = onSnapshot(doc(db, "users", fireUser.uid), (doc) => {
if (doc.exists()) {
setMe(doc.data())
console.log('looking for duplicate - userbar unsub')
} else {
console.log("No such document!")
}
})
return () => unsub()
}, [me])
return (
<>
{user && (
<div className="user-bar">
<p className="oneHunid">Welcome
<span className="bold"> {user.displayName}!</span>
</p>
<Avatar src={me.photoURL} />
</div>
)}
</>
)
}
You do not want to have [me] as a dependency when inside the useEffect() you call setMe(). That leads to an infinite loop:
me gets a new value, so render
me has a new value, so run useEffect()
useEffect() calls setMe()
go to # 1
Based on the above, it looks like what you really want is to have your useEffect() be dependent on the value of auth.currentUser. So get that value outside of the useEffect() and then check to be sure it is not null. If it is not null, then start the onSnapshot() listener.
BTW: if it would be of help, check out the user profiles with Firebase Auth project I developed. In particular, check out the FirebaseProvider.js which is where I configure the Firebase project and get all of its services, and AuthProvider.js which provides login, logout, registration and "user profile".

How to show loading state only if data is not received yet. Next.js ssr

I have multiple getServerSideProps in my project and I have a header which displays pages and I have to wait for a page to be opened once I click upon it since I need data to be fetched. Once they are fetched the page will be open.
One approach I used to show user a loading state is to use routeChangeStart BUT I stumbled upon one problem and so I would like not to use this case.
If I go on a page and the data is fetching I want to show user a spinner or some indicator and once the data is fetched I want to stop the indicator/spinner.
As you probably figured out, getServerSideProps runs on the server and is blocking. The fetch request needs to complete before the HTML is sent to the user (i.e., the page is changed). So if you want to show a loading indicator, you need to move that fetch request to the client.
For instance, if you probably have a page with this basic structure:
export default function Page({ data }) {
return <div>{data.name}</div>
}
export async function getServerSideProps() {
const response = await fetch('https://example.com/api')
const data = await response.json()
return {
props: { data },
}
}
const fetcher = url => fetch(url).then(res => res.json());
export default function Page() {
const { data } = useSWR('https://example.com/api', fetcher)
if (!data) return <LoadingSpinner />
return <div>{data.name}</div>
}
Or if you don't need SWR and can use a simple fetch request:
export default function Page() {
const [data, setData] = useState()
useEffect(() => {
fetch('https://example.com/api')
.then(async(response) => {
const json = await response.json()
setData(json)
})
})
if (!data) return <LoadingSpinner />
return <div>{data.name}</div>
}
P.S. If the initial fetch request in getServerSideProps used sensitive information (e.g., API secret credentials), then go ahead and setup a Next.js API route to handle the sensitive part and then fetch the new route.
I just used routeChangeStart.
I didn't want to use it since router.push('/map') didn't work in pages/index.tsx file but I solved this issue by creating a new component putting router.push in useeffect and rendering a loader.
routeChangeStart was in _app.js and because of this in index.js router.push() didn't work - I tested it
routeChangeStart - how it works?
When we click on a page the data is being fetched on the server and the page will only be displayed to us once the data is fetched. So we can make the next thing, we can just intercept the route change.
When we click on a link(we wait for data to fetch) we set loading state in routeChangeStart to true and if we moved to another page(it means we fetched the data) we invoke routeChangeComplete which runs once we moved to the route we wanted to, and here we set loading state to false. And after this I just pass the loading state using React Context

React Firebase - retrieving photoURL from users collection to render in comments section

I have a React app with Firebase backend. I have a users collection in the database which stores the photoURL of the user.
When a user comments on a post, instead of saving that photoURL each time into the comments collection I just want to render the photoURL from the users collection.
That way if the user updates their photoURL it is updated on all the comments plus there's no duplication of data.
On each comment I have saved the uid in the comment collection. I just can't quite figure out how to map through all the comments and take the uid and use that to get the photoURL from the users collection. Below is what I have so far.
I'm new to React so apologies if this is a simple fix.
const [comments, setComments] = useState([])
// This is used to get all the comments
useEffect(() => {
let unsubscribe
if (postId) {
unsubscribe = db.collection('posts').doc(postId).collection('comments').orderBy('timestamp', 'desc').onSnapshot((snapshot) => {
setComments(snapshot.docs.map((doc) => doc.data()))
})
}
return () => {
unsubscribe()
}
}, [postId])
// This is for adding the comment to the comments collection
const postComment = (event) => {
event.preventDefault()
db.collection('posts').doc(postId).collection('comments').add({
text: comment,
username: user.displayName,
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
photoURL: user?.photoURL, // This does not save anything because it's taking form auth user and not user collection
uid: user?.uid
})
setComment('')
setCommentActive(true)
setCommentsArrow(!commentsArrow)
}
// This is a function to get the photoURL of any user based on the uid
async function getImageURL(uid) {
return (await db.collection("users").doc(uid).get()).data().photoURL
}
// Rendering the comments
return (
<div style={{ transition: "height 0.3s linear" }} className={commentActive ? null : "hidden"}>
<div className="post__comments">
{comments.map((comment) => (
<div className="post__comment">
<Avatar
className="post__avatar"
alt=""
src={getImageURL(comment.uid)} // Calls the getImageURL function and passes in the uid from the comment
/>
<p><strong>{comment.username}</strong> {comment.text}</p>
</div>
))}
</div>
</div>
)
Here are the 2 options that I see are viable:
I see you are storing the users' UID in the comment doc, then make a separate request to Firestore (users collection) and then fetch their photoURL from that document.
This is what I prefer and seems to be the most viable way but to do so you would have to store the user images in Firebase Storage or any storage service. There you can structure your directory like /images/<user_uid>. So if my UID in the comment is 'uid123' then you would look for the image in Firebase Storage.
A download URL from Firebase Storage looks like:
https://firebasestorage.googleapis.com/v0/b/<project_id>.appspot.com/o/users%2F<userId>.png?alt=media&token=token
Now it does has that token at the end so you may have to use the getDownloadURL method or make your bucket public from the Google Cloud Console. But the format of URL will remains consistent i.e. https://domain.tld/path/image.png
This means users can't just simply paste an image URL to update their photo but they'll have to upload the image and you'll have to store it somewhere.
If you don't want to use storage or the 2nd method above, then you would have to make additional calls to the database to get users' profile picture.
Edit:
Function to get user's image URL from UID:
async function getImageURL(uid) {
return (await db.collection("users").doc(uid).get()).data().photoURL
}
Then in the component below:
{comments.map((comment) => (
<div className="post__comment">
<Avatar
className="post__avatar"
alt=""
src={getImageURL(comment.uid)}
/>
<p><strong>{comment.username}</strong> {comment.text}</p>
</div>
))}

Adding reset password functionality to react / redux login functionality

I used the following login with react/redux tutorial to build a signup / signin functionality into my React app, however I did not realize until recently that I now also need a reset-password / forgot-password functionality.
This feature is not a part of the tutorial at all, and I am simply wondering if anybody has any suggestions as to how I can go about this?
Let me know if I can share any info about my app that will help with this, or if there's a better place to post this type of question. I'm holding off on sharing more on the app as I think it's redundant given the info in the tutorial is nearly exactly how my signup / signin is setup.
Thanks!
After the user enters the proper credentials that you state (usually username, email, or both)
Make an api call to your backend that creates a password reset token. Store it in the database and, in one form or another, associate it with the user (usually it's the same database entry).
Send an email to the user with a link that has the password reset token embedded into it. Have a route in your react-router routes that will handle the url you link to.
Have the route mount a component that has a componentDidMount, which takes the token and makes an api to the backend to validate the token.
Once validated, open a ui element in the react component that allows the user to set a new password
Take the new password, password confirmation, and reset token and make an api call to the backend to change the password.
Delete the reset token in the backend after successful password change
// in your routes file
<Route path="/password_reset/:token" component={PasswordResetValidator}/>
//
class PasswordResetValidator extends React.Component {
state = {password: '', passwordReset: '', isValidated: false}
async componentDidMount() {
const response = await validatePasswordResetToken(this.props.token)
if (response.ok) {
this.setState({ isValidated: true })
} else {
// some error
}
}
handleSubmit = () => {
const { token } = this.props
const { password, passwordReset } = this.state
sendPasswordResetData({password, passwordReset, token})
// probably want some confirmation feedback to the user after successful or failed attempt
}
render() {
if(this.state.isValidated) {
return (
<div>
<input />
<input />
<button onClick={this.handleSubmit}>Set new password</button>
<div>
)
}
return // something while token is being validated
}
}
Obviously you need to make your own text input handlers. You should also have error handling, and good ui feedback to the user. But ultimately, that's all at your discretion.
Best of luck

Resources