I'm having a weird, issue, actually, I'm working with react-redux in a react-native app.
when the user logs in, everything goes smoothly in the user profile, but once I close the app and go back again, I'm getting a weird JSON format.
User reducer :
import AsyncStorage from '#react-native-async-storage/async-storage';
export function userReducer(
state = AsyncStorage.getItem('user') ? AsyncStorage.getItem('user') : null,
action,
) {
switch (action.type) {
case 'LOGIN':
return action.payload;
case 'LOGOUT':
return null;
case 'VERIFY':
return { ...state, verified: action.payload };
case 'USER_LIST_RESET':
return { users: [] };
default:
return state;
}
}
Login Form:
const submitForm = async () => {
if (isValidForm()) {
try {
const { data } = await axios.post(
'https://c36a-196-235-44-112.eu.ngrok.io/login',
{
email,
password,
},
);
dispatch({ type: 'LOGIN', payload: data });
AsyncStorage.setItem('user', JSON.stringify(data));
console.log('Logged In');
navigation.navigate('home');
} catch (error) {
console.log(error.message);
}
}
};
Getting the user in the userProfile :
const { user } = useSelector(state => ({ ...state }));
console.log(user)
The first Data I get once I login :
{"first_name": "John", "id": "63b0a010c015dd4b1f5fdb2c", "last_name": "Doe", "message": "Register Success ! please activate your email to start", "picture": "pic.png", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzYjBhMDEwYzAxNWRkNGIxZjVmZGIyYyIsImlhdCI6MTY3Mjk1MjU3NCwiZXhwIjoxNjczNTU3Mzc0fQ.XI9JnC4eeOQb5YJkNCC-Bqw4F9gpA1Xm6k_qJdkaXuw", "username": "JohnDoe", "verified": false}
The second data I get once I close and re open the app :
{"_A": null, "_x": 0, "_y": 1, "_z": "{\"id\":\"63b0a010c015dd4b1f5fdb2c\",\"username\":\"JohnDoe\",\"picture\":\"pic.png\",\"first_name\":\"John\",\"last_name\":\"Doe\",\"token\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzYjBhMDEwYzAxNWRkNGIxZjVmZGIyYyIsImlhdCI6MTY3Mjk1MTY3NiwiZXhwIjoxNjczNTU2NDc2fQ.lVLg3pjfTFGt1K63v76sucinIUZJgOxSujSox12Xy0s\",\"verified\":false,\"message\":\"Register Success ! please activate your email to start\"}"}
I was trying to get the same data if I close the app, but I'm not, I'm actually getting another JSON form that is not compatible with the logic I'm working with.
AsyncStorage.getItem() returns a Promise, it is an asynchronous function:
https://reactnative.dev/docs/asyncstorage#getitem
So when retrieving an item you will either need to use async/await syntax to wait for the promise to resolve before you can do something with the retrieved item, or you will need to specify a callback function when calling AsyncStorage.getItem() in which you do something with the retrieved item.
Related
I am starting to learn React Native and using Supabase with React is pretty different than using it with Flutter. Right now, I have gotten email sign-in/up working and listening for auth changes to switch navigation stacks. I believe my two issues are related because the user/session might not be getting saved when updated.
The first issue is that every time the app is opened, the sign-in screen is shown, even though I signed in before closing it. I tried to set som options, including localStorage but that did not help. Below is my supabaseClient.tsx file.
const options = {
auth: {
localStorage: AsyncStorage as any,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
},
};
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, options);
export { supabase };
The second issue is that I am trying something simple, just showing the displayName that the signed-in user has set on the HomeScreen, but the getUser() and the user from getSession() both return null so I cannot get the user.id. This causes the displayName to be undefined and a blank screen. Below is the code I am using to get the displayName.
export function HomeScreen() {
const [displayName, setDisplayName] = useState();
useEffect(() => {
(async () => {
const user = (await supabase.auth.getSession()).data.session?.user;
console.log("User: " + user);
const { data, error } = await supabase
.from("profiles")
.select()
.eq("uid", user?.id);
if (error === null) {
data!.map((data) => {
const name = data.displayName;
console.log(name);
setDisplayName(name);
});
} else {
console.log(error.message);
}
console.log("Name: " + displayName);
})();
}, [setDisplayName]);
return (
<View>
<Text>{displayName}</Text>
</View>
);
}
I had defined localStorage as any because of a tutorial I saw.
I needed to change this:
const options = {
auth: {
localStorage: AsyncStorage as any,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
},
};
to this:
const options = {
auth: {
localStorage: AsyncStorage,
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
},
};
So, I've been reacting for quite some time. I am facing a problem handling API and making it interact with small widgets in mobile app.
What I am trying to do ?
I am on a Screen(A) and on click of a button I go to Screen(B). Simple. Now in Screen(B) I fill up a form and submit it. While the form is submitting I have to show a Loading component. Finally the API responds if the request was "Success" or a "Failure".
If it was Success - Navigate back to Screen(A) and show a toast message(Boom..boom..) on screen(A).
If it Failed - Be in Screen(B) and show a toast message(yes, with a failure message).
My Approach
Let's start with reducers. I have following reducer state -
{
forSubmitRequest: false, // false - API has been trigerred to submit form
formSubmitRequestOver: true, // true - request is over
formSubmitRequestStatus: true // true - success
}
Now my actions are as follows -
case FORM_SUBMIT_REQUEST:
return {
...state,
formSubmitRequest: true,
formSubmitRequestOver: false,
formSubmitRequestStatus: false,
};
case FORM_SUBMIT_REQUEST_SUCCESS:
return {
...state,
formSubmitRequestOver: true,
formSubmitRequestStatus: true
};
case FORM_SUBMIT_REQUEST_FAILED:
return {
...state,
formSubmitRequestOver: true,
formSubmitRequestStatus: false,
};
case FORM_SUBMIT_REQUEST_DOWN:
return {
...state,
formSubmitRequest: false,
formSubmitRequestOver: true
};
Here's my coding logic in Screen(B)
const [formSubmitReq, setFormSubmitReq] = useState(false);
const [showErrorFormSubmitToast, setShowErrorFormSubmitToast] = useState(false);
useEffect(() => {
if (showErrorFormSubmitToast) {
Toast.show({
type: 'error',
text1: 'Error',
text2: 'Could not submit.',
topOffset: ResponsiveSize(0),
onHide: () => {
setShowErrorFormSubmitToast(false);
},
});
}
}, [showErrorFormSubmitToast]);
if (
formSubmitReq &&
props.teacher.formSubmitRequest &&
!props.teacher.formSubmitRequestOver
) {
return <Loading msg="Submitting form..." />;
}
if (
formSubmitReq &&
props.teacher.formSubmitRequest &&
props.teacher.formSubmitRequestOver
) {
if (props.teacher.formSubmitRequestStatus) {
props.navigation.goBack();
return <></>;
} else {
setFormSubmitReq(false);
setShowErrorFormSubmitToast(true);
props.handleCreateFormSubmitDown();
}
}
Logic in Screen(A)
const [showSuccessFormSubmitToast, setShowSuccessFormSubmitToast] =
useState(false);
useEffect(() => {
if (showSuccessFormSubmitToast) {
Toast.show({
type: 'success',
text1: 'Success',
text2: 'Successfully submitted.',
onHide: () => {
setShowSuccessFormSubmitToast(false);
},
});
}
}, [showSuccessFormSubmitToast]);
if (
!showSuccessFormSubmitToast &&
props.teacher.formSubmitRequest &&
props.teacher.formSubmitRequestOver &&
props.teacher.formSubmitRequestStatus
) {
console.log('Prep show toast');
setShowSuccessFormSubmitToast(true);
props.handleCreateFormSubmitDown();
}
Lastly this function - handleCreateFormSubmitDown just fires action - FORM_SUBMIT_REQUEST_DOWN
Gist of the code ->
I am trying to show Toast on Screen(A) only when request was success and request was actually fired. Not it may happen that request was success and I came back to Screen(A) now I simply navigate to Screen(B) so I have to make sure toast isn't visible as request wasn't fired. Now when reducer state updates then Screen(A) also gets rendered causing some nast things.
Can anyone point out what strategies they follow in such scenarios and where I could improve here?
One way to do this is to utilize route params from react-navigation
In screen B
...
useEffect(() => {
// Navigate to screen A with a route param
navigation.navigate(A, { displaySuccessToast: true });
}, [showSuccessFormSubmitToast])
In Screen A
...
useEffect(() => {
if (route.params.displaySuccessToast) {
Toast.show({
type: 'success',
text1: 'Success',
text2: 'Successfully submitted.',
onHide: () => {
// Hide Toast Action
},
});
}
}, [route.params]);
Other wise you can use a global state management solution like redux
Its the same logic. You need to update a flag in the global state. Then navigate to the earlier screen and check if this flag is set and render the toast message.
I have an Ionic Project with Vuex. I have created a store:
const store = new Vuex.Store({
state: {
user: localStorage.getItem('userdata') || {}
},
getters: {
getUser(state) {
return state.user
}
},
mutations: {
setUser(state, user) {
state.user = user
},
destroyUser(state) {
state.user = null
},
},
actions: {
retrieveUser(context) {
return new Promise((resolve, reject) => {
axios.get('v1/user')
.then(response => {
const user = response.data.data
localStorage.setItem('userdata', JSON.stringify(user))
context.commit('setUser', user)
resolve(user)
})
.catch(error => {})
})
},
}
})
This part works perfect as expected. My localstore holds the JSON string. Now i tried to return the string with the getUser getter JSON.parsed. This doesn't work, because it gives me a parse error which makes no sense, because the string works perfectly fine.
When I try to load the userdata in the vue component like this
export default {
data() {
return {
user: [],
}
},
mounted() {
this.loadUserData()
},
methods: {
loadUserData() {
let userData = JSON.parse(this.$store.getters.getUser)
this.user = userData
}
},
}
It returns the JSON Data as Proxy ( ?? )
Proxy {id: 27, name: "English", firstname: "Harriet", fullname: "Harriet English", number: null, …}
[[Handler]]: Object
[[Target]]: Object
[[IsRevoked]]: false
(it's sample data, so no real name shown ) which I cannot use.
I have also tried to use the state variable, the localstorage content, which did not work...
How can I access my JSON data?
When you save the user data after your API call, you are storing it in localStorage as JSON.stringify(user) but you are updating the store with just the raw user data. I guess you should update your API call handler to:
const user = response.data.data;
const strUser = JSON.stringify(user);
localStorage.setItem('userdata', strUser);
context.commit('setUser', strUser);
This should allow you to parse the data the way you are trying to in your component, which should work whether state.user has been initialised with the localStorage data, or if it has been updated after the API call.
Im trying to integrate Paymentez (a payments processor) into my site. I get "success" or "failure" responses after doing a test transaction but cant change data in the Vue component (want to show a modal/dialog).
data: function() {
return {
success: false,
failure: false
}
},
created() {
this.paymentCheckout = new window._PAYMENTEZ.modal({
client_app_code: "***********", // Client Credentials
client_app_key: "***************", // Client Credentials
locale: "es", // User's preferred language (es, en, pt). English will be used by default.
env_mode: "stg", // `prod`, `stg`, `local` to change environment. Default is `stg`
onOpen: function () {
console.log("modal open");
},
onClose: function () {
console.log("modal closed");
},
onResponse: function (response) {
// The callback to invoke when the Checkout process is completed
/*
In Case of an error, this will be the response.
response = {
"error": {
"type": "Server Error",
"help": "Try Again Later",
"description": "Sorry, there was a problem loading Checkout."
}
}
When the User completes all the Flow in the Checkout, this will be the response.
response = {
"transaction":{
"status": "success", // success or failure
"id": "CB-81011", // transaction_id
"status_detail": 3 // for the status detail please refer to: https://paymentez.github.io/api-doc/#status-details
}
}*/
console.log(response);
document.getElementById("response").innerHTML = JSON.stringify(
response
);
},
});
/* what I want is something like:
if(response.transaction.status == "success") {
this.success = true
}
else if(response.transaction.status == "failure") {
this.failure = true
}
else if (response.error) {
// show error
}
*/
I've added the Paymentez library via CDN and initialized it in a created() hook.
this.success and this.failure remain false.
Cant access this inside the callback
To be able to access the outer this, your callbacks need to be arrow functions. Example:
// ...
onResponse: response => {
this.success = true; // <= works! (changes your component's reactive prop).
console.log('console is your friend', this);
console.log(response);
}
//...
If they're normal functions they overwrite the outer this with their own scope (and that's what this points to inside them). Read more here.
I have been trying to solve this problem for a few hours now to no avail. Could someone help me spot the problem?
The error I am getting is:
Error: [vuex] Do not mutate vuex store state outside mutation handlers
Here is my login script section with the offending function in login()
<script>
import { auth, firestoreDB } from "#/firebase/init.js";
export default {
name: "login",
props: {
source: String
},
////////
layout: "login",
data() {
return {
show1: false,
password: "",
rules: {
required: value => !!value || "Required.",
min: v => v.length >= 8 || "Min 8 characters",
emailMatch: () => "The email and password you entered don't match"
},
email: null,
feedback: null
};
},
methods: {
login() {
if (this.email && this.password) {
auth
.signInWithEmailAndPassword(this.email, this.password)
.then(cred => {
//this.$router.push("/");
this.$store.dispatch("user/login", cred);
console.log()
this.$router.push("/forms")
console.log("DONE")
})
.catch(err => {
this.feedback = err.message;
});
} else {
this.feedback = "Please fill in both fields";
}
},
signup() {
this.$router.push("signup");
}
}
};
</script>
import { auth, firestoreDB } from "#/firebase/init.js";
export const state = () => ({
profile: null,
credentials: null,
userID: null
})
export const getters = {
getinfo:(state) =>{
return state.credentials
},
isAuthenticated:(state)=>{
if (state.credentials != null) {
return true
} else {
return false
}
}
}
export const mutations = {
commitCredentials(state, credentials) {
state.credentials = credentials
},
commitProfile(state, profile) {
state.profile = profile
},
logout(state){
state.credentials = null,
state.profile = null
}
}
export const actions = {
login({commit},credentials) {
return firestoreDB.collection("Users").where('email', '==', auth.currentUser.email).get()
.then(snapshot => {
snapshot.forEach(doc => {
let profile = {...doc.data()}
commit("commitCredentials", credentials)
commit("commitProfile", profile)
})
}).catch((e) => {
console.log(e)
})
},
credentials({ commit }, credentials) {
commit("commitCredentials",credentials)
},
logout() {
commit("logout")
},
}
I have checked that there is no where else that is directly calling the store state.
I have worked out that if I don't do the commitCredentials mutation which mutates credentials, the problem doesn't happen.
Another note to add, the error keeps printing to console as if it were on a for loop. So my console is flooded with this same message.
I am pretty sure this is to do with the firebase auth sign in and how the Credential object is being changed by it without me knowing, but I can't seem to narrow it down.
Any help would be very much welcomed.
Found the answer.
https://firebase.nuxtjs.org/guide/options/#auth
signInWithEmailAndPassword(this.email, this.password)
.then(cred)
"Do not save authUser directly to the store, since this will save an object reference to the state which gets directly updated by Firebase Auth periodically and therefore throws a vuex error if strict != false."
Credential object is constantly being changed by the firebase library and passing the credential object is just passing a reference not the actual values itself.
The solution is to just save the values within the object.