How to make localStorage persist even after reloading of the browser? - reactjs

I have made a react app with mongodb and express with a simple authentication system. After signup, the User model with my credentials gets saved to the localStorage like this:
{
bio: ""
email: "myemail#gmail.com"
name: "myName"
role: "user"
_id: "5e9b42d31040cb0fc54c6936"
}
After login I want to update the bio property, or to add some since is empty initialy, so in order to do that I have made a axios call like this:
async function updateBiography (evt) {
evt.preventDefault();
try {
const res = await axios.put(`http://localhost:5000/biography/${id}`, { bioVal });
setBioVal(res.bio);
} catch (error) {
console.log(error);
}
}
So this thing is returning the updated value of bio, saving it to the database, no problem here. But I want to update the value of bio in the browser as well, and I want that to happen instantly as I submit. In order to achieve that I have done this:
React.useEffect(() => {
const currentUser = JSON.parse(localStorage.getItem('user'));
currentUser['bio'] = bioCurrentValue;
localStorage.setItem('user', JSON.stringify(currentUser));
}, [reload]);
After this the item in localStorage is like this:
{
bio: "the new value",
...
}
Awesome. The problem I have is that after reloading the browser the localStorage does not keep the update.
After reloading the item in the localStorage it becomes as before:
{
bio: "",
...
}
How can I make the updates persist in the localStorage?

Local storage is normally persistent, but you're subject to the browser settings of the user.
If in some private browsing mode, or with some other privacy extensions, it's common for the storage to be wiped out. Nothing you can do about this if this is what the user wants.

Related

Firestore takes a long time to save data upon signing up and nothing happens after that

So I'm trying to save the user's information when they create an account into Firestore and this is my AuthContext.js
function signUp(user){
return auth.createUserWithEmailAndPassword(user.email,user.password)
.then(async res => {
await updateTeacherDatabase(res.user.uid, user)
})
.catch(error => console.log(error))
}
the auth variable is from firebase.js:
const app = firebase.initializeApp({...})
export const auth = app.auth(); //authentication
this is the updateTeacherDatabase function:
function updateTeacherDatabase(userID, user){
return db.collection('teacher-info').doc(userID).set({
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
role: 'teacher'
}).catch(error => console.log(error))
}
When the user creates and account and they submits the form, it should redirect them to the home page if there's no error. However, mine just stays on the sign up page for a really long time. My submit button is grey out to indicate that the form is sending data to the back end.
I checked my firestore Auth, the user is created but not the document in teacher-info collection. When I commented out the .then part, everything works as usual, or when I deleted the async await, it worked but the database is not updated. My console.log is clear, there's no error so I really don't know how to fix this issue?

How do I structure a fetch request after an other?

I have a React blog and I am using Firebase as a back-end. I use createUserWithEmailAndPassword method and after authentication users are redirected to blog where they can start adding blogs.
I store their posts in a firestore collection "posts". No problem with that.
I also want a user object to be created after login with the user preferences. Let's say a specific theme each one has chosen.
I create a collection called "users" at firestore and where I will store each ones object {user: "random#hotmail.com, theme: "dark" ,isAdmin: false, etc} using addDoc method.
I want this object to be created once though and not every time a user logs in.
How do the check on that "users" collection if the user already exists?
I get the collection getDocs(userCollectionRef) and then I filter the data let's say by a property to see if there is that object there.
And if not I want to add the document using addDoc method.
this is the request:
useEffect(() => {
const createUserData = async () => {
const data = await getDocs(usersCollectionRef);
const docs = data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
const userData = docs.filter((doc) => doc.user === user.email);
if (userData.length === 0) {
await addDoc(usersCollectionRef, {
user: user.email,
isAdmin: false,
theme: "dark",
});
}
};
if (user) {
createUserData();
}
}, [user, usersCollectionRef]);
It seems like I am trying
to check and add to the collection at the same time and this is why it doesn't work.
Any ideas?
Should I have an in-between step where I store what I'm getting from the getDocs in a state or something and then do the second request?
Can anyone explain please?
I changed the if statement to a return like so and it worked.
return (
userData.length === 0 &&
(await addDoc(usersCollectionRef, {
user: user.email,
wordGoal: wordGoal,
}))
);
I guess I return the second promise after the first now that's why
I guess when the getDocs request happens because its asynchronous the data then are empty then the code proceeds to the if statement which at that point is true (we have no records) and so executes it.

Next JS router not changing page in async function

I am very new to promises and JS in general, but I am working on creating a register page using a tutorial on YouTube. I am currently using next.js with react and TypeScript to redirect to the user to the home page if no errors occur when registering (no email, password too short, etc.), but the router won't redirect the user within an async onSubmit function.
Here is the current non-working code. The useRegisterMutation hook just creates a function that takes in the input fields and creates a user in the db for me:
const Register: React.FC<registerProps> = ({}) => {
const router = useRouter();
const [register] = useRegisterMutation();
return (
<Wrapper>
<Formik initialValues={{first_name: "", last_name: "", email: "", password: ""}}
onSubmit={async (values, {setErrors}) => {
const response = await register({
variables: {
email: values.email,
pass: values.password,
fname: values.first_name,
lname: values.last_name
}
});
// A user is returned, meaning there were no errors
if (response.data?.createUser?.user) {
await router.push("/");
// An error was returned, meaning that the user was not created
} else if (response.data?.createUser?.errors) {
setErrors(setErrorMap(response.data.createUser.errors));
}
}}>
// ----------- Unimportant HTML -----------
</Formik>
</Wrapper>
);
}
export default Register;
When I remove async / await from the onSubmit function, the router begins correctly working, but the if statements do not work correctly because they are expecting response to be a promise, so they fail every time. Is there some trick to get the router to push to the correct url while still keeping the function as a promise?
Your async code seems to be fine, and you're saying the redirect works without async.
It seems that you might not be getting what you're expecting to get.
I'd log the response and response.data and all of the subsequent properties that you need for your if statement.
p.s.
Feel free to share the tutorial, you might've missed something or maybe it has a mistake.

What's the proper way to use Firebase Authentication along with Firestore?

Hi all—I'm building an app using Next.js and Firebase, both brand new technologies for me. It's a simple app where a user creates an account and must log in. If the user doesn't create an account, the app is useless—they can't move on to the next screen, which is a dashboard. Anyway, when they log in, they can then create a trip/vacation itinerary. I'm using Firebase Auth for auth and Firestore (not real-time db) as my db. My goal is that when a user logs in, the user can see every itinerary that they created and no one else's. It should be full CRUD. This is the first time I've done this sort of authentication as well, so that's likely adding to my confusion.
I know that my code isn't right, but it sort of worked. What keeps happening is that it seems like there's a lag when a user logs in and out. I've tested this on my local copy. When I log out and then log back in as a different user, it tells me that the uid is null. Anywhere from 1 - 30 minutes later (seriously), all of a sudden the page loads for the uid that I logged in with! Everything that I've read says that there's a lag with the authentication, but I couldn't really find a solution other than just pointing out the problem—so basically writing a console log that says who's logged in at the time and then the same when they've logged out. Also, I watched / read tons of tutorials, so maybe it's my code? I'm so sorry in advance for this novel of code—I'll organize as best as I can!
Here's my config info, so I'm referring to Firebase as fire. The sign-in method is email and password, and everything looks as it should in Firebase as far as capturing that information on the Authentication screen.
import firebase from 'firebase';
const firebaseConfig = {
apiKey: 'AIzaSyB-xEPETXSfjboKe5H0kPUu-ZdRDGfszmA',
authDomain: "where-to-jess.firebaseapp.com",
databaseURL: 'https://where-to-jess-default-rtdb.firebaseio.com/',
projectId: "where-to-jess",
storageBucket: "where-to-jess.appspot.com",
messagingSenderId: "914509599583",
appId: "1:914509599583:web:80cdf3e4090417b0f35cea"
};
try {
firebase.initializeApp(firebaseConfig);
} catch(err){
if (!/already exists/.test(err.message)) {
console.error('Firebase initialization error', err.stack)}
}
const fire = firebase;
export default fire;
When a user creates an account, they're also added to a collection 'users' in my db. I am using React Hooks (for the first time) as well. Their email is their username to login, but I'm capturing their email in the db. They are also immediately logged in upon account creation. This part also works.
const handleSubmit = (e) => {
e.preventDefault();
setPassword('');
fire.auth().createUserWithEmailAndPassword(userName, password)
.then(() => {
fire.firestore().collection('users').doc(fire.auth().currentUser.uid)
.set({
firstName: firstName,
lastName: lastName,
email: userName
})
.catch(error => {
console.log('user wasn\'t added to db: ', error);
})
})
.catch(error => {
console.log('user wasn\'t able to create an account: ', error);
})
router.push('/users/dashboard')
};
This is my login code:
const handleLogin = (e) => {
e.preventDefault();
fire.auth()
.signInWithEmailAndPassword(username, password)
.catch((error) => {
console.log('user wasn\'t able to login: ', error);
})
setUsername('')
setPassword('')
router.push('/users/dashboard')
};
Now for the fun part! This is my code for form submission for the itinerary. What I'm trying to achieve here is to have this newly created itinerary attached to their uid in the 'users' db. I'm leaving out all the form stuff because it's super long. This also seems to work—I can see it coming in in the db for whichever account I'm using.
const handleSubmit = (e) => {
e.preventDefault();
fire.firestore()
.collection('users').doc(fire.auth().currentUser.uid).collection('itineraries')
.add({
//
})
.catch(error => {
console.log('itinerary not added to db ', error)
})
router.push('/users/dashboard')
}
Here's where it all went to heck! I suspect it's because I'm cutting corners, which I'll explain next. This dashboard should show ONLY itineraries that the current logged-in user created. If the current logged-in user didn't create any itineraries, I'd get an error saying that the uid was null. SO, my workaround was to just create a fake itinerary manually in the db on their account (since I was testing) and give the tripName value as null. This seems to work, but this is where the weird login / logout stuff happens.
export default function Dashboard() {
const router = useRouter();
const [itineraries, setItineraries] = useState([]);
const [loggedIn, setLoggedIn] = useState(false);
fire.auth()
.onAuthStateChanged((user) => {
if (user) {
console.log(user.email + " is logged in!");
setLoggedIn(true)
} else {
setLoggedIn(false)
console.log('User is logged out!');
}
})
useEffect(() => {
const unsubscribe =
fire.firestore()
.collection('users').doc(fire.auth().currentUser.uid).collection('itineraries').where('tripName', '!=', 'null')
.onSnapshot(snap => {
const itineraries = snap.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setItineraries(itineraries);
return () => {
unsubscribe();
};
});
}, []);
const handleLogout = () => {
fire.auth()
.signOut()
router.push('/')
};
Lastly, here is the one rule that I have on the db. I got confused reading the rule docs, and I feel like I cut a corner here.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read
allow write
}
}
}
Again, I'm really sorry for ALL of that code. This is my first time using React Hooks, Next, and Firebase—so it's a mashup of Firebases's docs, tutorials, and my own code. I'd appreciate ANY help or advice here.
That rule will allow all access to all documents in your db at present. You want something like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user_id}{
allow read, write: if request.auth != null && request.auth.uid == user_id;
}
}
}
That will allow access only to users that are authenticated
This was 100% user error on my part, but I wanted to share since some of my issues seem pretty common. In addition to AspiringApollo's advice above, I had my function completely out of order (as I mentioned, hook newbie). The above plus structuring my function like this fixed it:
useEffect(() => {
const unsubscribe =
fire.auth()
.onAuthStateChanged((user) => {
if (user) {
let uid = user.uid
console.log(user.email + ' is logged in!');
setLoggedIn(true)
// all the things you want to do while the user is logged in goes here
} else {
setLoggedIn(false)
console.log('user is logged out!');
}
return () => {
unsubscribe();
};
});
}, []);
Still open to suggestions and more sets of eyes because I know this is a little messy!

Remember me with redux storage

I have userDetails in redux store and I used redux persist with redux storage to save the details after loads page or after closing my app.
The problem is, when I logged in and I don't checked the rememberMe in index.js is checked if !rememberMe and do logOut() right after login.
So I need to check just at the entrance into the app for the first time and if !rememberMe I will call logOut() but if userDetails !== 'emepty' && rememberMe I will do login() for get valid token.
Why I need to check this it in the first time? because I need to reset the uesrDetails to guest details if !rememberMe.
This is my index.js:
const setBeforeLists = useCallback(
(lists) => {
beforeListsChanged(lists);
},
[beforeListsChanged]
);
const setUserDetails = useCallback(
(userDetails) => {
userDetailsChanged(userDetails);
},
[userDetailsChanged]
);
useEffect(() => {
//userDetails.id === 'empty' is guest.
if (
userDetails.id !== 'empty' &&
!userDetails.rememberMe
) {
logOut(setUserDetails, setBeforeLists);
}
}, [
setBeforeLists,
setUserDetails,
userDetails
]);
When you refresh the app, the store will be reset. The token is gone. As we expected.
So, we can’t use the state to store the authentication token. This is where AsyncStorage comes onto the stage.
I'm replying to a react-native application.
If it's a web application use session storage.
Let’s import AsyncStorage in the React Native project:
import { AsyncStorage } from "react-native";
Then, let’s create two functions,setToken and getToken, for storing and retrieving the persistent data. These are the main methods of this tutorial. Here, we are storing and retrieving the user data from/to AsyncStorage.
async storeToken(user) {
try {
await AsyncStorage.setItem("userDetails", JSON.stringify(user));
} catch (error) {
console.log("Something went wrong", error);
}
}
async getToken(user) {
try {
let userDetails = await AsyncStorage.getItem("userDetails");
let data = JSON.parse(userDetails);
console.log(data);
} catch (error) {
console.log("Something went wrong", error);
}
}
componentDidMount() {
this.getToken();
}
Let’s prove it. The token is thereafter you quit & reopen the app. Try it out yourself. Now, our app automatically logs you in after each session.
Hope this helps!

Resources