I have a uid from firebase, but it is stored like this:
checkout.js
firebase.auth().onAuthStateChanged((user) => {
if(user) {
console.log(user.uid)
}
});
the user.uid stores the uid I need to pass to another page. this uid is stored in checkout.js, and I need to get it to profile.js to use the uid there. how would I do this?
please let me know if more code is needed. I am using react / js
I literally just copy and pasted that code into the profile.js and it worked. 😑
use a context store
create the store and the hook
const FirebaseAuthContext = createContext()
export function useAuth() {
const context = useContext(FirebaseAuthContext)
if (!context && typeof window !== 'undefined') {
throw new Error(`useAuth must be used within a FirebaseAuthContext`)
}
return context
}
Create the Provider
export function FirebaseAuthProvider(props) {
const [authState, setAuthState] = useState({
isLoggedIn: false,
currentUser: null,
pending: true,
})
useEffect(() => {
const unregisterAuthObserver = auth().onAuthStateChanged(async (currentUser) => {
if (currentUser) {
setAuthState({. isLoggedIn: true, pending: false, currentUser})
} else {
setAuthState({ ...authState, currentUser: null, pending: false })
}
})
return () => unregisterAuthObserver()
}, [])
return <FirebaseAuthContext.Provider value={authState}>{props.children}</FirebaseAuthContext.Provider>
Wrap your app with provider
<FirebaseAuthProvider><App /></FirebaseAuthProvider>
Then wherever you need to validate the user.
const { currentUser, pending } = useAuth();
if (currentUser && pending) //pending authentication
if (currentUser) // user is login, uid is in currentUser.uid
Related
i have an async thunk function that gets a user from database and sets it to the state, but i want to set the user to local storage after getting it,
//SignIn.tsx
const userState = useAppSelector((state) => state.user);
const handleSignIn = async (e: React.SyntheticEvent) => {
e.preventDefault();
const user = await dispatch(signIn({ email, password }));
localStorage.setItem('currentUser', JSON.stringify(userState));
console.log(userState);
navigate('/shop');
};
//userSlice.ts
export const signIn = createAsyncThunk(
'user/signIn',
async ({ email, password }: { email: string; password: string }) => {
const { user }: UserCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
const data = await getUser(user);
return { data, user };
}
);
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(signIn.fulfilled, (state, action) => {
state.email = action.payload.user.email;
state.token = action.payload.user.refreshToken;
state.id = action.payload.user.uid;
state.avatarUrl = action.payload.data.avatarUrl;
state.lastName = action.payload.data.lastName;
state.name = action.payload.data.name;
});
}
im awaiting the dispatch and then trying to set the user to local storage using updated state, idk why the state doesnt update after awaiting the dispatch, instead of updated state im setting the PREVIOUS state to the local storage, please help, im struggling with this for an hours
Actually, you are storing current state in local storage, you have to store fetched user
const { user } = await dispatch(signIn({ email, password }));
localStorage.setItem('currentUser', user);
Or you can store user directly in createAsync function
You can unwrap and then store
const onClick = () => {
dispatch(fetchUserById(userId))
.unwrap()
.then((originalPromiseResult) => {
localStorage.setItem('currentUser', JSON.stringify(originalPromiseResult));
})
.catch((rejectedValueOrSerializedError) => {
// handle error here
})
}
I'm new to Firebase and using it for the first time to create a user system where you can sign up, login, and logout using Firebase authentication. There is a username state variable, and also a user state variable. Upon sign up the username is stored correctly, however the user variable is still returning null unless I refresh the page, then after the user shows correctly. Is there something that I'm missing?
const [open, setOpen] = useState(false);
const [openSignIn, setOpenSignIn] = useState(false);
const [modalStyle] = useState(getModalStyle);
const classes = useStyles();
const [username, setUsername] = useState("")
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [user, setUser] = useState(null);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((authUser) => {
if (authUser) {
// user is logged in...
console.log(authUser);
setUser(authUser.displayName);
if (authUser.displayName) {
} else {
//if user was just created
return authUser.updateProfile({
displayName: username,
});
}
} else {
setUser(null);
}
});
return () => {
unsubscribe();
};
}, [user, username]);
const signUp = (event) => {
event.preventDefault();
auth
.createUserWithEmailAndPassword(email, password)
.then((authUser) => {
authUser.user.updateProfile({
displayName: username
})
})
.catch((error) => alert(error.message));
setOpen(false)
}
const signIn = (event) => {
event.preventDefault();
auth
.signInWithEmailAndPassword(email, password)
.catch((error) => alert(error.message));
setOpenSignIn(false)
}
In recent versions of the web SDK (since v4.0.0 in May 2017), when the Auth#onAuthStateChanged() listener is fired changed to only on sign-in and sign-out but it used to be any changes to the ID token associated with the user (like updating the displayName). If you wanted to revert to the old functionality, you would instead use Auth#onIdTokenChanged().
useEffect(() => {
const unsubscribe = auth.onIdTokenChanged((authUser) => {
if (authUser) {
// user is logged in...
console.log(authUser);
setUser(authUser.displayName);
if (authUser.displayName) {
} else {
//if user was just created
return authUser.updateProfile({
displayName: username,
});
}
} else {
setUser(null);
}
});
return () => {
unsubscribe();
};
}, [user, username]);
I recommend renaming user to something else like userDisplayName as you should reserve user for use with instances of firebase.auth.User. This will prevent confusion with other code samples you stumble across.
Personally, I would recommend splitting this functionality out of the onAuthStateChanged handler like so:
// outside your component
function updateDisplayName(user, username) {
if (!user) {
return Promise.reject(new Error("You must be signed in first!"));
}
return user.updateProfile({
displayName: username
});
}
// inside your component
// user == undefined -> still verifying auth token
// user == null -> signed out
// user is a firebase.user.User -> user signed in
const [user, setUser] = useState(() => firebase.auth().currentUser || undefined);
// username == null -> signed out/still verifying user token/displayName not set
// username is a string -> user signed in & displayName set
const [username, setUsername] = useState(user ? user.displayName : null);
// userIsLoading == true -> still verifying user token, lock the form/show loading icon
// userIsLoading == false -> `user` is resolved and ready for use
const userIsLoading = user === undefined;
// a value to store the proposed new username, connect this to an input
const [newUsername, setNewUsername] = useState("");
// an error message for the input of `newUsername`
const [usernameError, setUsernameError] = useState(null);
// keep `user` & `username` (if available) up to date
useEffect(() => auth.onAuthStateChanged((user) => {
setUser(user);
setUsername(user ? user.displayName : null);
}), []);
// attach this to a button - don't connect to "onChange" of "newUsername"
// note: this returns a Promise that finishes when the username was either updated or failed to update
const onChangeUsernameSubmit = () => {
return updateDisplayName(user, newUsername)
.then(() => {
// updated successfully. update state
setUsername(newUsername);
setUsernameError(null);
})
.catch((err) => {
// failed to change username. invalid value? expired token?
setUsernameError("username not changed: " + (err.message || "something unexpected went wrong"));
});
}
I am using Redux and to persist my auth. Using redux toolkit I have an action that checks onAuthChanged from Firebase Auth.
export const persistedSession = () => {
return async (dispatch) => {
dispatch(requestLogin());
firebaseAuth.onAuthStateChanged(async (user) => {
if (user) {
const currentUser = await createUserProfileDocument(user);
currentUser.on("value", (snapshot) => {
var data = snapshot.val();
if (data !== null) {
//Success
}
});
}
});
};
};
Then when I check if a document exists for this user and if not create one.
export const createUserProfileDocument = async (user) => {
if (!user) return;
const { uid, displayName, email } = user;
const userReference = database.ref("users");
userReference.once("value", (snapshot) => {
if (!snapshot.hasChild(uid)) {
userReference.child(uid).set({
...
});
}
});
return userReference;
};
So here is where my issue is. Upon creating my user displayName is always null how can I update user profile correctly? It seems like onAuthChanged is triggered before it can make an update so that displayName will always be null and document will not contain displayName property
export const registerUser = (value) => {
const { firstName, surname, email, password } = value;
return async (dispatch) => {
firebaseAuth
.createUserWithEmailAndPassword(email, password)
.then(async (result) => {
const user = result.user;
user.updateProfile({
displayName: `${firstName} ${surname}`,
})
})
.catch((error) => {
console.log("Unable to create user profile: ", error);
});
};
};
Calling updateProfile does not trigger onAuthStateChanged, as the authentication state doesn't change. There is no way to delay the triggering of onAuthStateChanged here either, as you can only set the display name after the user account has been created, which signs them in too.
Two options that I can think of:
Create the user, set their display name, sign them out, and sign them in again. The latter will then trigger another onAuthStateChanged witht he display name included.
Explicitly write the display name to the database too, when you also call updateProfile.
My goal is to:
Load one document and get the value for "account"
Then load a new document with "account" as one of the document names in firestore. I am using this.account for the path
<script>
// eslint-disable-next-line
import firestore from "firestore";
// eslint-disable-next-line
import NewEmployee from "#/components/updatePopups/NewEmployee";
import db from "#/components/fbInit";
import firebase from "firebase";
export default {
// eslint-disable-next-line
components: { NewEmployee },
data() {
return {
account: "",
users: [],
acc:[],
};
},
computed: {
computed() {
const user = firebase.auth().currentUser;
const info = [];
db
.collection("userProfiles")
.doc(user.uid)
.get()
.then(doc => {
const doc1content = doc.data();
return doc1content.account;
console.log(doc1content.account)
});
}
},
created() {
const user = firebase.auth().currentUser;
let account = computed();
let empRef = db.collection('userProfiles').doc(this.account).collection('employees');
let empCollection = empRef.get()
.then(snapshot => {
this.users = [];
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
const data = {
id: doc.id,
Name: doc.data().Name,
GroupHex: doc.data().GroupHex,
DeviceType: doc.data().DeviceType,
UserIDInt: doc.data().UserIDInt,
SuperID: doc.data().SuperID,
misc: doc.data().misc,
Status: doc.data().Status,
};
this.users.push(data)
});
})
},
};
</script>
I keep getting undefined errors or value can't be "" or """
account is undefined
ReferenceError: computed is not defined
The reason I am not doing this all in one created hook is because I would like to use the snapshot feature to reload the page when there is a change automatically and I had trouble doing that when it was nested inside another doc.get(). I would like to have the "account" variable loaded and stored when the page is loaded(or before).
Any help would be greatly appreciated.
Hello All 👋🏻 I have a question about our favorite Hooks API!
What am I trying to do?
I am trying to fetch photos from some remote system. I store the blob urls for these photos in my reducer state keyed by an id.
I have a helper function wrapped in the memoized version returned by the useCallback hook. This function is called in the useEffect I have defined.
The Problem ⚠️
My callback a.k.a the helper function depends on part of the reducer state. Which is updated every time a photo is fetched. This causes the component to run the effect in useEffect again and thus causing an infinite loop.
component renders --> useEffect runs ---> `fetchPhotos` runs --> after 1st photo, reducer state is updated --> component updates because `useSelector`'s value changes ---> runs `fetchPhotos` again ---> infinite
const FormViewerContainer = (props) => {
const { completedForm, classes } = props;
const [error, setError] = useState(null);
const dispatch = useDispatch();
const photosState = useSelector(state => state.root.photos);
// helper function which fetches photos and updates the reducer state by dispatching actions
const fetchFormPhotos = React.useCallback(async () => {
try {
if (!completedForm) return;
const { photos: reducerPhotos, loadingPhotoIds } = photosState;
const { photos: completedFormPhotos } = completedForm;
const photoIds = Object.keys(completedFormPhotos || {});
// only fetch photos which aren't in reducer state yet
const photoIdsToFetch = photoIds.filter((pId) => {
const photo = reducerPhotos[pId] || {};
return !loadingPhotoIds.includes(pId) && !photo.blobUrl;
});
dispatch({
type: SET_LOADING_PHOTO_IDS,
payload: { photoIds: photoIdsToFetch } });
if (photoIdsToFetch.length <= 0) {
return;
}
photoIdsToFetch.forEach(async (photoId) => {
if (loadingPhotoIds.includes(photoIds)) return;
dispatch(fetchCompletedFormPhoto({ photoId }));
const thumbnailSize = {
width: 300,
height: 300,
};
const response = await fetchCompletedFormImages(
cformid,
fileId,
thumbnailSize,
)
if (response.status !== 200) {
dispatch(fetchCompletedFormPhotoRollback({ photoId }));
return;
}
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
dispatch(fetchCompletedFormPhotoSuccess({
photoId,
blobUrl,
}));
});
} catch (err) {
setError('Error fetching photos. Please try again.');
}
}, [completedForm, dispatch, photosState]);
// call the fetch form photos function
useEffect(() => {
fetchFormPhotos();
}, [fetchFormPhotos]);
...
...
}
What have I tried?
I found an alternative way to fetch photos a.k.a by dispatching an action and using a worker saga to do all the fetching. This removes all the need for the helper in the component and thus no useCallback and thus no re-renders. The useEffect then only depends on the dispatch which is fine.
Question ?
I am struggling with the mental modal of using the hooks API. I see the obvious problem, but I am not sure how could this be done without using redux middlewares like thunks and sagas.
Edit:
reducer function:
export const initialState = {
photos: {},
loadingPhotoIds: [],
};
export default function photosReducer(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case FETCH_COMPLETED_FORM_PHOTO: {
return {
...state,
photos: {
...state.photos,
[payload.photoId]: {
blobUrl: null,
error: false,
},
},
};
}
case FETCH_COMPLETED_FORM_PHOTO_SUCCESS: {
return {
...state,
photos: {
...state.photos,
[payload.photoId]: {
blobUrl: payload.blobUrl,
error: false,
},
},
loadingPhotoIds: state.loadingPhotoIds.filter(
photoId => photoId !== payload.photoId,
),
};
}
case FETCH_COMPLETED_FORM_PHOTO_ROLLBACK: {
return {
...state,
photos: {
...state.photos,
[payload.photoId]: {
blobUrl: null,
error: true,
},
},
loadingPhotoIds: state.loadingPhotoIds.filter(
photoId => photoId !== payload.photoId,
),
};
}
case SET_LOADING_PHOTO_IDS: {
return {
...state,
loadingPhotoIds: payload.photoIds || [],
};
}
default:
return state;
}
}
You could include the photoIdsToFetch calculation logic into your selector function, to reduce the number of renders caused by state change.
const photoIdsToFetch = useSelector(state => {
const { photos: reducerPhotos, loadingPhotoIds } = state.root.photos;
const { photos: completedFormPhotos } = completedForm;
const photoIds = Object.keys(completedFormPhotos || {});
const photoIdsToFetch = photoIds.filter(pId => {
const photo = reducerPhotos[pId] || {};
return !loadingPhotoIds.includes(pId) && !photo.blobUrl;
});
return photoIdsToFetch;
},
equals
);
However the selector function isn't memoized, it returns a new array object every time, thus object equality will not work here. You will need to provide an isEqual method as a second parameter (that will compare two arrays for value equality) so that the selector will return the same object when the ids are the same. You could write your own or deep-equals library for example:
import equal from 'deep-equal';
fetchFormPhotos will depend only on [photoIdsToFetch, dispatch] this way.
I'm not sure about how your reducer functions mutate the state, so this may require some fine tuning. The idea is: select only the state from store that you depend on, that way other parts of the store will not cause re-renders.