Cant get Auth token passed into reducer in redux - react-native

I have my userKey in the 'userLogin' function below, but when i get ready to send the userkey into my reducer, its not there.
I've tried updating my action to wrapping up the setUserId function into 'userLogin', but i keep getting null in my reducer (the actions are triggering, but its not making its way to the reducer. Help please!
action file
export const userLogin = userKey => {
return async dispatch => {
await AsyncStorage.setItem('userKey', userKey).then(userKey => {
console.log('userKey')
console.log(userKey) // have the key here
dispatch(setUserId(userKey))
})
}
}
export const setUserId = userKey => {
console.log('userKey')
console.log(userKey) // comes up as Null
return {
type: SET_USERID,
userKey: userKey
}
}
reducer file
import { SET_USERID, REMOVE_USERID } from '../actions/actionTypes'
const initialState = {
userKey: ''
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_USERID:
return {
...state,
userKey: action.userKey
};
case REMOVE_USERID:
return {
...state,
userKey: null
};
default:
return state;
}
};
export default reducer;

try this:
export const userLogin = userKey => {
return async dispatch => {
try {
await AsyncStorage.setItem('userKey', userKey)
console.log('userKey')
console.log(userKey) // have the key here
dispatch(setUserId(userKey))
} catch (ex) {
console.log(ex);
}
}
}

Related

change toolkit redux state inside of fetch

I just want to update state outside of component after server give me failed response.
the project is react native .
After a lot of online search on website like this , I learn that i need to import my store and use dispatch method. but when i import storage and dispatch it, it give me this error: Property 'crypto' doesn't exist .
this is my feachService.js code:
import { URL } from 'react-native-url-polyfill';
import { store } from '../store/index'
import { errorSliceActions } from '../store/slices/global/errorSlice'
export default (url, isGet) => {
let headers = {
Referer: new URL(url).origin,
}
headers['X-Requested-With'] = 'XMLHttpRequest'
return fetch(
url,
{
method: (isGet ? 'GET' : 'POST'),
headers: headers
})
.then(function (res) {
return res.json();
})
.then(function (res)
{
if(res && res.isSuccess == false && res.message) {
store.dispatch(errorSliceActions.add(res.message));
}
return {json: function() { return res; }};
})
.catch(function (error) { console.log(error.message); })
};
this is my storage js file:
import { configureStore } from '#reduxjs/toolkit';
import errorSliceReducer from './slices/global/errorSlice';
export const store = configureStore({
reducer: {
errorSlice: errorSliceReducer
}
});
this is my error slice.js file:
import { createSlice } from "#reduxjs/toolkit"
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
const errorSlice = createSlice({
name: "errorSlice",
initialState: {
errors: [
]
},
reducers:
{
add: (state, action) => {
const newItem = {
id: uuidv4(),
message: action.payload
};
return [...state, newItem];
},
remove: (state, action) => {
return { errors: state.errors.filter(function(item) { return item.id != action.payload; }) }
}
}
})
export const errorSliceActions = errorSlice.actions;
export default errorSlice.reducer
and this is my code for executing fetch request
async function loadInfo (inputSearch, inputPage) {
if(config.dataurl) {
console.log(inputSearch);
setIsLoading(true);
const hasQuestionMark = config.dataurl.indexOf('?') > 0;
let targetUrl = '/test/test' + config.dataurl + (!hasQuestionMark ? '?' : '&');
targetUrl += 'page=' + inputPage;
targetUrl += '&search=' + inputSearch;
console.log(targetUrl);
const response = await feachService(appJSon.baseUrl + targetUrl , true);
const responseData = await response.json();
setIsLoading(false);
if(responseData.pagination)
setPagination(responseData.pagination);
if(!responseData.results)
setItems([]);
else
setItems(responseData.results);
}
}
useEffect(() => {
loadInfo('', 1).catch(function (error) { console.log(error.message); });
}, [])
what am i doing wrong ?

Why is firebase authentication not persisting on refresh in Ionic with Vue and Pinia?

I've been generally following along with this code here: https://github.com/aaronksaunders/ionic-v6-firebase-tabs-auth
The issue I'm having is my auth state is not persisting when I refresh the page when using ionic serve and loading the app in the web browser.
code for pinia store:
import { defineStore } from "pinia";
import { User } from "firebase/auth";
import {
Profile,
getProfile,
setProfile,
} from "#/firebase/helpers/firestore/profileManager";
import { onSnapshot, Unsubscribe, doc } from "#firebase/firestore";
import { db } from "#/firebase/connectEmulators";
import { getAuth } from "#firebase/auth";
import {
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
createUserWithEmailAndPassword,
updateProfile as updateAuthProfile,
} from "firebase/auth";
import errorHandler from "#/helpers/errorHandler";
/**#see {#link Profile} */
export enum UserType {
DNE,
uploader,
checker,
host,
}
interface State {
user: User | null;
profile: Profile | null;
error: null | any;
unsub: Unsubscribe | null;
}
export const useUserStore = defineStore("user", {
state: (): State => {
return {
user: null,
profile: null,
error: null,
unsub: null,
};
},
getters: {
isLoggedIn: (state) => state.user !== null,
//DEV: do we need this to be a getter?
userError: (state) => {
if(state.error){
switch (state.error.code) {
case "auth/user-not-found":
return "Email or Password incorrect!";
case "auth/wrong-password":
return "Email or Password incorrect!";
default:
return state.error;
}
}
return null;
},
/**
* #see Profile
*/
getType: (state): UserType => {
if (state.user === null) return UserType.DNE;
if (!state.profile) return UserType.DNE;
if (state.profile.locations.length > 0) return UserType.host;
if (state.profile.queues.length > 0) return UserType.checker;
return UserType.uploader;
},
},
actions: {
initializeAuthListener() {
return new Promise((resolve) => {
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
console.log("AuthListener Initialized");
if (user) {
console.log("AuthListener: User exists!");
this.user = user;
getProfile(user.uid).then((profile) => {
if (profile) {
this.profile = profile;
this.initializeProfileListener();
} else {
this.profile = null;
if (this.unsub) this.unsub();
}
});
} else {
console.log("AuthListener: User does not exist!");
this.user = null;
}
resolve(true);
});
});
},
/**
*
* #param email email for login
* #param password password for login
*/
async signInEmailPassword(email: string, password: string) {
try {
const auth = getAuth();
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
this.user = userCredential.user ? userCredential.user : null;
this.error = null;
return true;
} catch (error: any) {
console.log(typeof error.code);
console.log(error.code);
this.user = null;
this.error = error;
return false;
}
},
async logoutUser() {
try {
const auth = getAuth();
await signOut(auth);
this.user = null;
this.profile = null;
this.error = null;
if (this.unsub) this.unsub();
return true;
} catch (error: any) {
this.error = error;
return false;
}
},
async createEmailPasswordAccount(
email: string,
password: string,
userName: string,
refSource: string
) {
try {
const auth = getAuth();
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
//Add username to fireauth profile
//DEV: test for xss vulnerabilities
await updateAuthProfile(userCredential.user, { displayName: userName });
//create user profile data in firestore
let profile: Profile | undefined = new Profile(
userCredential.user.uid,
refSource
);
await setProfile(profile);
profile = await getProfile(userCredential.user.uid);
//set local store
this.user = userCredential.user ? userCredential.user : null;
this.profile = profile ? profile : null;
this.error = null;
//TODO: send email verification
return true;
} catch (error: any) {
this.user = null;
this.error = error;
return false;
}
},
initializeProfileListener() {
try {
if (!this.profile) errorHandler(Error("Profile not set in state!"));
else {
const uid = this.profile.uid;
const unsub: Unsubscribe = onSnapshot(
doc(db, "profiles", uid),
(snapshot) => {
const fbData = snapshot.data();
if (!fbData)
errorHandler(Error("Profile Listener snapshot.data() Null!"));
else {
const profile = new Profile(
snapshot.id,
fbData.data.referralSource
);
profile.data = fbData.data;
profile.settings = fbData.settings;
profile.locations = fbData.locations;
profile.queues = fbData.queues;
profile.checkers = fbData.checkers;
profile.uploadHistory = fbData.uploadHistory;
profile.hostLevel = fbData.hostLevel;
this.profile = profile;
}
},
(error) => {
errorHandler(error);
}
);
this.unsub = unsub;
}
} catch (error) {
errorHandler(error as Error);
}
},
},
});
main.ts where I initialize auth listener:
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { IonicVue } from "#ionic/vue";
/* Core CSS required for Ionic components to work properly */
import "#ionic/vue/css/core.css";
/* Basic CSS for apps built with Ionic */
import "#ionic/vue/css/normalize.css";
import "#ionic/vue/css/structure.css";
import "#ionic/vue/css/typography.css";
/* Optional CSS utils that can be commented out */
import "#ionic/vue/css/padding.css";
import "#ionic/vue/css/float-elements.css";
import "#ionic/vue/css/text-alignment.css";
import "#ionic/vue/css/text-transformation.css";
import "#ionic/vue/css/flex-utils.css";
import "#ionic/vue/css/display.css";
/* Theme variables */
import "./theme/variables.css";
/* PWA elements for using Capacitor plugins */
import { defineCustomElements } from "#ionic/pwa-elements/loader";
/* Pinia used for state management */
import { createPinia } from "pinia";
import { useUserStore } from "./store/userStore";
const pinia = createPinia();
const app = createApp(App)
.use(IonicVue, {
// TODO: remove for production
mode: process.env.VUE_APP_IONIC_MODE,
})
.use(pinia);
defineCustomElements(window);
//get the store
const store = useUserStore();
store.initializeAuthListener().then(() => {
app.use(router);
});
router.isReady().then(() => {
app.mount("#app");
});
I've tried refactoring main.ts to mount the app inside the callback for initialize auth listener and I've tried making my code exactly like the code in the main.ts of the above link. Neither solved the issue.
I also looked at the question here: https://stackoverflow.com/a/67774186/9230780
Most of the points in the answer shouldn't be related because I'm currently using the firebase emulators to test the app.
Even still, I've verified my api key is correct.
I can see that cookies are created in the browser when I launch the app, so I don't think its an issue with them being wiped.
Ideally I'd like to avoid implementing #capacitor/storage here because it shouldn't be necessary.
I do plan to implement this library to handle authentication for ios and android: https://github.com/baumblatt/capacitor-firebase-auth
but that shouldn't be pertinent to the web version of the app.
Edit:
Realized I was missing a piece of code pertinent to the question. Not sure how I didn't copy it over. Code added is the initialize Profile listener function.
I ended up doing a refactor of the pinia store and that solved the issue. I believe the issue may have been caused by how the auth listener called initializeProfileListener. I didn't have code in the auth listener to check if the profile listener was already initialized, so everytime the authstate changed or it would initialize a new profile listener without unsubbing the old one. I'm not absolutely certain that is what was causing the issue though.
Below is the new code that functions properly.
pinia store:
import { defineStore } from "pinia";
import { User } from "firebase/auth";
import {
Profile,
getProfile,
profileListener,
} from "#/firebase/helpers/firestore/profileManager";
import {
fbCreateAccount,
fbSignIn,
fbAuthStateListener,
fbSignOut,
} from "#/firebase/helpers/firestore/authHelper";
import {Unsubscribe} from "#firebase/firestore";
import errorHandler from "#/helpers/errorHandler";
/**#see {#link Profile} */
export enum UserType {
DNE,
uploader,
checker,
host,
}
interface State {
user: User | null;
profile: Profile | null;
error: null | any;
unsub: Unsubscribe | null;
}
export const useUserStore = defineStore("user", {
state: (): State => {
return {
user: null,
profile: null,
error: null,
unsub: null,
};
},
getters: {
isLoggedIn: (state) => state.user !== null,
//DEV: do we need this to be a getter?
userError: (state) => {
if (state.error) {
switch (state.error.code) {
case "auth/user-not-found":
return "Email or Password incorrect!";
case "auth/wrong-password":
return "Email or Password incorrect!";
default:
return state.error;
}
}
return null;
},
/**
* #see Profile
*/
getType: (state): UserType => {
if (state.user === null) return UserType.DNE;
if (!state.profile) return UserType.DNE;
if (state.profile.locations.length > 0) return UserType.host;
if (state.profile.queues.length > 0) return UserType.checker;
return UserType.uploader;
},
},
actions: {
initializeAuthListener() {
return new Promise((resolve) => {
fbAuthStateListener(async (user: any) => {
if (user) {
this.user = user;
const profile = await getProfile(user.uid);
if (profile) {
this.profile = profile;
//TODO: initialize profile listener
if(this.unsub === null) {
this.initializeProfileListener();
}
}
}
resolve(true);
});
});
},
/**
*
* #param email email for login
* #param password password for login
*/
async signInEmailPassword(email: string, password: string) {
try {
const userCredential = await fbSignIn(email, password);
this.user = userCredential.user ? userCredential.user : null;
this.error = null;
return true;
} catch (error: any) {
console.log(typeof error.code);
console.log(error.code);
this.user = null;
this.error = error;
return false;
}
},
async logoutUser() {
try {
await fbSignOut();
this.user = null;
this.profile = null;
this.error = null;
if (this.unsub) this.unsub();
return true;
} catch (error: any) {
this.error = error;
return false;
}
},
async createEmailPasswordAccount(
email: string,
password: string,
userName: string,
refSource: string
) {
try {
const { user, profile } = await fbCreateAccount(
email,
password,
userName,
refSource
);
//set local store
this.user = user ? user : null;
this.profile = profile ? profile : null;
this.error = null;
//TODO: send email verification
return true;
} catch (error: any) {
this.user = null;
this.error = error;
return false;
}
},
initializeProfileListener() {
try {
if (this.user) {
const unsub = profileListener(
this.user?.uid,
async (profile: any) => {
if (profile) {
this.profile = profile;
}
}
);
this.unsub = unsub;
}
} catch (error) {
errorHandler(error as Error);
}
},
},
});
authHelper.ts
import { auth } from "#/firebase/firebase";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
updateProfile as updateAuthProfile,
} from "#firebase/auth";
import { Profile, setProfile, getProfile } from "./profileManager";
/**
* #param email
* #param password
* #param userName
* #param refSource #see profileManager
* #returns
*/
export const fbCreateAccount = async (
email: string,
password: string,
userName: string,
refSource: string
) => {
//DEBUG: creating a user works but throws an error.
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
if (userCredential) {
//add username to fireauth profile
await updateAuthProfile(userCredential.user, { displayName: userName });
//create user profile data in firestore
let profile: Profile | undefined = new Profile(
userCredential.user.uid,
refSource
);
await setProfile(profile);
profile = await getProfile(userCredential.user.uid);
//TODO: errorHandling for setProfile and getProfile
return {
user: userCredential.user,
profile: profile,
};
} else {
return {
user: null,
profile: null,
};
}
};
/**
*
* #param email
* #param password
* #returns UserCredential {#link https://firebase.google.com/docs/reference/js/auth.usercredential.md?authuser=0#usercredential_interface}
*/
export const fbSignIn = async (email: string, password: string) => {
const userCredential = signInWithEmailAndPassword(auth, email, password);
//TODO: add call to add to profile signins array
return userCredential;
};
export const fbSignOut = async () => {
await signOut(auth);
return true;
};
/**
* #see {#link https://firebase.google.com/docs/reference/js/auth.md?authuser=0&hl=en#onauthstatechanged}
* #param callback contains either user or null
*/
export const fbAuthStateListener = (callback: any) => {
onAuthStateChanged(auth, (user) => {
if (user) {
//user is signed in
callback(user);
} else {
//user is signed out
callback(null);
}
});
};

React Redux rejectWithValue() not working

I'm currently facing a problem, when I try to reject a value when my node-fetch request fail, thunkApi.rejectWithValue() isn't working. However when my request is pending or when It's fulfilled, It's working fine.
Here's my slice :
export const userSlice = createSlice({
name: "user",
initialState: initialState as User,
reducers: {
...
},
extraReducers: (builder) => {
...
builder.addCase(changePassUser.pending, (state) => {
GGLog("FETCHING CHANGEPASS API...");
state.isFetching = true;
return state;
});
builder.addCase(changePassUser.fulfilled, (state, { payload }) => {
GGLog("FULFILLED CHANGEPASS:", JSON.stringify(payload));
state.isFetching = false;
state.isSuccess = true;
state.isError = false;
return state;
});
// eslint-disable-next-line #typescript-eslint/no-explicit-any
builder.addCase(changePassUser.rejected, (state, { payload }: any) => {
GGLog("REJECTED CHANGEPASS:", JSON.parse(payload));
state.isFetching = false;
state.isError = true;
state.errorMessage = payload.data;
return state;
});
},
});
Here's my thunk :
export const changePassUser = createAsyncThunk(
"users/password/update",
async ({ oldpassword, newpassword }: RegisterParameters, thunkAPI) => {
try {
const res = await changePassApi.changePass.return({
oldpassword: oldpassword,
newpassword: newpassword,
});
GGLog("API_CHANGEPASS_RES:", res);
const data = await res.json();
if (res.ok) {
GGLog("API_DATA_RESPONSE_OK: ", data);
const tokenData = JSON.stringify(res.headers);
const token = JSON.parse(tokenData).map["x-auth"];
await localStorage.store("token", token);
return data;
} else {
GGLog("API_DATA_RESPONSE_NOK: ", data);
return thunkAPI.rejectWithValue(data);
}
} catch (e) {
GGLog("Error while fetching Login API => ", e);
return thunkAPI.rejectWithValue(e);
}
}
);
And here's the result in the console :
Console output
Any ideas ? Am I missing something ?
Thanks :)
Okay I've found my problem, I was just focused on the thunk and didn't pay attention to the promise rejection. I was trying to parse a JSON that does'nt exist... Just remove the GGLog("REJECTED CHANGEPASS:", JSON.parse(payload));in the slice.
It's working fine now !

React Native async await dispatch store

So I want to make a login feature, in here server will validate first if the username or password is correct or not.
I'm using store, react - redux.
Here is my code when login button pressed
const [statusLogin,setStatusLogin] = useState(null)
let loginInfo = []
function loginButton(){
(async () => {
loginInfo = {username:username,password:password}
const { status } = await dispatch(getUser(loginInfo))
if (status==1){
console.log(status,'in status if 1')
setStatusLogin('granted')
}else{
console.log(status,'in status if else')
setStatusLogin(null)
}
})();
}
Here is my store that suppose to return value 1 or else
if it returned value 1 geb statusLogin will changed as granted
export function getUser(body){
return dispatch =>{
if (!body){
setTimeout(() => {
console.log('no username/pass')
}, 2000);
}else{
setTimeout(() => {
console.log('username/pass validated returning with value 1')
}, 2000);
}
}
}
help me please
This might help
...
function loginButton() {
(async () => {
loginInfo = { username: username, password: password };
await dispatch(getUser(loginInfo, callback));
})();
}
function callback = (status) => {
if (status == 1) {
console.log(status, "in status if 1");
setStatusLogin("granted");
} else {
console.log(status, "in status if else");
setStatusLogin(null);
}
};
reducer.js
export function getUser(body, callback){
return dispatch =>{
if (!body){
setTimeout(() => {
console.log('no username/pass');
callback(0);
}, 2000);
}else{
setTimeout(() => {
console.log('username/pass validated returning with value 1')
callback(1);
}, 2000);
}
}
}
you can use like:
usEffect(( )=>{
if(statusLogin) getUser()
},[statusLogin])
Another thing, your function should not passed callback in. Instead of using callback to change the state, you can use dispatch to modify the reducer.

Redux : Getting null value after calling Reducer in React Native?

I'm making an API Call to get a value that i will store in my reducer. Although when i'm calling my reducer i'm getting null.
here is my code to get the value :
export function getValue(token) {
return fetch('URI', { method: 'GET'})
.then(response => response.json())
.then((response) => {
return response.value;
});
}
Then i have my redux :
const UPDATE_VALUE = 'user/UPDATE_VALUE';
...
const initialState = {
myValue: null,
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case UPDATE_VALUE:
return {
...state,
myValue: action.value
};
default:
return state;
}
}
export function updateValue(value) {
return {
type: UPDATE_VALUE,
value,
};
}
And here i call the api to stock the value. I'm getting the value and it existes but when i want to store and get it's value i'm getting null :
const getMyValue = new Promise((resolve) => {
return getMyValue(token).then((value) => {
updateValue(value === null ? null : value);
resolve();
});
});