This works fine in development, but when I deploy many of my functions break. E.g., I have a form that adds data to the database but when I submit it, it just refreshes the page but the data never makes it to the database.
onSubmit = () => {
firebase.auth().onAuthStateChanged((user) => {
const { phoneNumber, points } = this.state;
let pointsToAdd = Number(points);
if(user) {
const docRef = database.collection('users').doc(user.uid).collection('customers').doc(phoneNumber);
docRef.set({
points: pointsToAdd
})
.then(() => {
console.log('success');
})
.catch((error) => {
console.log(error);
});
} else {
window.location.href = '/';
}
});
}
If it's refreshing, then the user variable is null. So, you must not be signed in on your deployed version. Just add a firebase signin function first before calling that one!
https://firebase.google.com/docs/auth/web/password-auth
I guess you're not logged in on the non-dev environment, the current implementation will simply redirect when no user is found.
As feedback on your current submit-handler: I think you want to listen to authStateChanged when your components mounts instead of when the submit handler is called. Update the state accordingly and use the user from the state when reaching for the correct document.
For example:
componentDidMount () {
this.unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) this.setState({ user })
else window.location.href = '/'
})
}
componentWillUnmount () {
this.unsubscribe() // don't forget to unsubscribe when unmounting!
}
onSubmit = () => {
const { phoneNumber, points, user } = this.state;
let pointsToAdd = Number(points);
database.collection('users').doc(user.uid).collection('customers').doc(phoneNumber)
.set({
points: pointsToAdd
})
.then(() => {
console.log('success');
})
.catch((error) => {
console.log(error);
});
}
Related
I'm making an app and trying to get product data by it's id inside a modal in ionic 4.
I'm using typescript to do it but without luck.
Because de calls to firebase are asynchronous i cannot get the data that is held in firebase and also because i'm new to subject i cannot figured out the proper way to write the code.
I read about how to do it but i'm having a hard time to achieve it.
Here is my function that tries to grab product data from firebase.
It always logs empty on console.log('todo', todo).
async editProduct(id) {
const getTodo = docRef => {
setTimeout(() => {
docRef = this.afs.collection("products").doc(id);
docRef.get().subscribe((doc) => {
if (doc.exists) {
let data = doc.data();
return data;
} else {
console.log("No document.");
return false;
}
});
}, 2000)
}
getTodo(todo => {
console.log('todo', todo)
})
const modal = await this.modalCtrl.create({
component: AdminProductPage,
'id': id,
});
await modal.present();
}
There is something wrong with your "getTodo". Probable you are logging empty data with your code, I can give you the proper functional example:
myData
editProduct() {
this.afs.collection("products").doc(id)
.valueChanges()
.subscribe(data => {
console.log(data)
myData = data
})
}
getData() {
console.log(this.myData) // You will log it twice with this line
}
GOOGLE EXAMPLE
docRef.get().then((doc) => {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
https://firebase.google.com/docs/firestore/query-data/get-data?hl=es
so I'm using a popup to log my users in with firebase:
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then(async (result) => {
if (result.additionalUserInfo.isNewUser) {
// problem is this line
await setNewUserInformation(result.user.uid)
}
const { user } = result
setUser(user)
// and this line
window.location.href = 'newRoute'
})
.catch((error) => {
console.log('ERROR:', error)
})
}
so if I remove window.location.href = 'visited' this all works fine and it sets in firebase. I'm probably doing something stupid but I cant figure out how to wait for this function to fire setNewUserInformation and to complete before I move to the new page?
function code:
export const setNewUserInformation = (userId) => {
return {
type: 'SET_NEW_USER_INFORMATION',
userId,
}
}
this then has a redux observable epic listening to it:
return action$.pipe(
ofType('SET_NEW_USER_INFORMATION'),
mergeMap((action) => {
return from(
firebaseApp.database().ref(firebaseRef).update(userInformation),
).pipe(
mergeMap(() => {
return [updatedUserInformationSuccess()]
}),
catchError((error) => of(updatedUserInformationFailure(error))),
)
}),
)
setNewUserInformation() is an action creator, which is sync. You do not need to wait for it as it does not return anything useful to you logic. What you need to do, is move window.location.href = 'newRoute' to separate logic, and make it depend on state returned from action creators updatedUserInformationSuccess() and updatedUserInformationFailure(error). If your component is functional, put this logic in a useEffect. If it is a class component, use ComponentDidUpdate lifecycle method.
Use it like below
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then(async (result) => {
new Promise((resolve, reject) => {
if (result.additionalUserInfo.isNewUser) {
// problem is this line
setNewUserInformation(result.user.uid)
}
const { user } = result
resolve(user)
}).then((user)=>{
setUser(user)
// and this line
window.location.href = 'newRoute'
})
})
.catch((error) => {
console.log('ERROR:', error)
})
}
Because on then You can returned a Promise and resolve later. We could re-write the code above like this below:
const loginToApp = (provider) => {
firebaseApp
.auth()
.signInWithPopup(provider)
.then((result) => {
if (result.additionalUserInfo.isNewUser) {
// return for next resolve function
return setNewUserInformation(result.user.uid).then(() => result);
}
return result;
})
.then((result) => {
// after all above promises resolve
const { user } = result
setUser(user)
// and this line
window.location.href = 'newRoute'
})
.catch((error) => {
console.log('ERROR:', error)
})
}
Are you using React?
If yes, then you can simply use didUpdate Cycle to route to new url after successful action dispatched. Move your "window.location.href = 'newRoute'" under the ComponentDidUpdate with props check.
I am building a mobile app and I'm using Django REST Framework as a backend. And I am also using Redux. One of the API that I am using is to validate an OTP code. If the OTP code is matched, then I am retuning with the response from the server if this is a new user or not. If it's a new user then I'll redirect it to a registration screen, if not then I'll redirect it to the login screen, this is my problem.
I am storing the response of the server in variable named isNewUser in redux store. Then, I am accessing it Inside my component with useSelector. When I click on the button after I entered then OTP code, I dispatch two actions. First the one to validate the OTP. The second is either will be a dispatch for Login or dispatch for registration action, this is depends on the variable isNewUser which I am getting from redux store.
The problem is that, when I dispatch the first action, which is validation of the OTP and storing of the isNewUser variable, the value of this variable is not updated in my component until the next render, so I can't dispatch the second action until I click the button again so that the value of the variable is updated.
So how to fix that? I don't know if my implementation is correct or not or there is a better one.
Here is my code for the action, I didn't write the code for login and register actions yet
export const validateOTP = (otp, mobileNum) => {
return async dispatch => {
const response = await fetch("http://localhost:8000/api/validate_otp", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
body: JSON.stringify({
otp: otp,
mobile: mobileNum
})
});
if (!response.ok) {
const errorResData = await response.json();
console.log(errorResData);
}
const resData = await response.json();
if (resData.status === false) {
throw new Error(resData.detail);
} else {
const isNewUser = resData.isNewUser;
dispatch({
type: VALIDATE_OTP,
isNewUser: isNewUser
});
}
};
};
Here is my code for the reducer:
import { VALIDATE_OTP } from "../actions/auth";
const initialState = {
isNewUser: null
};
export default (state = initialState, action) => {
switch (action.type) {
case VALIDATE_OTP: {
const isNewUserVal = action.isNewUser;
return {
...state,
isNewUser: isNewUserVal
};
}
}
return state;
};
Here is a sample code from the React Native component:
const CodeEntryScreen = props => {
const dispatch = useDispatch();
const isNewUser = useSelector(state => state.auth.isNewUser)
const [error, setError] = useState();
useEffect(() => {
if (error) {
Alert.alert("An Error Occurred", error, [{ text: "Okay" }]);
}
}, [error]);
const validateOTPHandler = async () => {
setError(null);
try {
await dispatch(authActions.validateOTP(otp, mobileNum));
console.log(isNewUser)
if(isNewUser) {
// dispatch resgister action
}
else {
// dispatch login action
}
} catch (err) {
setError(err.message);
}
};
You can fix this issue with little modifications. The easier one is this:
1) Use dispatch return value in your validateOTPHandler
In your validateOTP function, you have this at the end:
dispatch({
type: VALIDATE_OTP,
isNewUser: isNewUser
});
Make your function to return that instead:
return dispatch({
type: VALIDATE_OTP,
isNewUser: isNewUser
});
With that change, in your component, you can access to the payload of your action this way:
const validateOTPHandler = async () => {
setError(null);
try {
const { isNewUser: isNew } = await dispatch(authActions.validateOTP(otp, mobileNum));
console.log(isNew)
if(isNew) {
// dispatch resgister action
}
else {
// dispatch login action
}
} catch (err) {
setError(err.message);
}
};
That is the easier change to make it work as you want.
2) useEffect
I think this is more similar to the flow you had in mind:
Valide the code (update the store)
Re-render: you got the new value
Now do something: login or register
But do that, you need to use useEffect in order to listen the changes you made this way:
const CodeEntryScreen = props => {
const dispatch = useDispatch();
const isNewUser = useSelector(state => state.auth.isNewUser)
const [error, setError] = useState();
const [success, setSuccess] = useState(false); // true when validateOTP succeeds
useEffect(() => {
if (error) {
Alert.alert("An Error Occurred", error, [{ text: "Okay" }]);
}
}, [error]);
useEffect(() => {
if (success) {
// validateOTP succeed... let's check isNewUser :)
if (isNewUser) {
// dispatch register
} else {
// dispatch login
}
}
}, [success, isNewUser]);
const validateOTPHandler = async () => {
setError(null);
setSuccess(false);
try {
await dispatch(authActions.validateOTP(otp, mobileNum));
setSuccess(true);
} catch (err) {
setError(err.message);
}
};
Is there a way in React JS to block a request if it is already doing it, i am talking of the same request.
EDIT:
This is my code:
const fetching = false;
export default (type, filter, dateFilter, position) => {
if(fetching) return Promise.reject(new Error('Request in progress'));
fetching = true;
return fetch(URL + `/search/${type}/${filter}/${dateFilter}/${position}/0/0`)
.then(response => Promise.all([response, response.json()]))
//!!! My problem is that now i cannot put .then(() => fetching = false))
// here.If i put it i get undefined in my then(res) of my getDataApi
// so i cannot make requests again because fetching stays in true.
}
for better understanding this is my console with:
.then(() => {
fetching = false;
console.log("fetching", fetching)
})
and without:
actions.js
export const fetchData = (type, filter, dateFilter, position) => {
return (dispatch, getState) => {
const state = getState();
dispatch(getData())
getDataApi(type, filter, dateFilter, position)
.then(res => {
console.log("RES", res)
if (res !== undefined) {
console.log("entro")
//here it doesnt enter if i put fething false above
// is like somehow the promise.all is not resolved if i
// put it above or under the then with the fetching =
// false but i need it, what can i do?
if (state.dataReducer.data.length === 0) {
dispatch(getDataSuccess(res[1]))
} else {
dispatch(getDataSuccess(res[1], state.dataReducer.data))
}
}
})
.catch((err) => console.log(9999, err))
}
}
Not sure you really need to over complicate this, hold some state that indicates your request is already in progress so subsequent requests can be ignored.
You don't mention how you are managing your app state so here's a very simple example based on your code
let fetching = false;
export default (type, filter, dateFilter, position) => {
if (fetching) return Promise.resolve();
fetching = true;
return fetch('...')
.then(response => {
// handle response
})
.catch(e => {
// handle error
})
.then(() => {
fetching = false; // reset state
});
}
I solved it.This is my code:
return fetch(URL + `/search/${type}/${filter}/${dateFilter}/${position}/0/0`)
.then(response => Promise.all([response, response.json()]))
.then(([response, responseObj]) => {
fetching = false;
return [response, responseObj];
})
.catch(err => {
fetching = false;
return Promise.reject(err); // If you want to handle the error in a chained .catch()
})
I have the following debounced function that gets called every time a user inputs into the username field. It is working as expected.
export const uniqueUsernameCheck = _.debounce(({ username }) => {
axios.post(`${API_URL}/signup/usernamecheck`, { username })
.then((res) => {
console.log('Is unique?', res.data.status);
})
.catch((error) => {
console.log(error);
});
}, 500);
However using redux-thunk I am trying to modify the function so that I can dispatch actions within my function. This is what I have:
export const uniqueUsernameCheck = _.debounce(({ username }) => {
console.log('I can see this');
return (dispatch) => {
console.log('But not this');
dispatch({ type: USERNAME_CHECK });
axios.post(`${API_URL}/signup/usernamecheck`, { username })
.then((res) => {
dispatch(authError(res.data.error));
})
.catch((error) => {
console.log(error);
});
};
}, 500);
The problem lies in that the above code no longer fires off my post request like the initial function did and nothing ever gets dispatched. I know I'm doing something wrong but can't figure out what.
EDIT:
This is how I've set up my store
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
Take a look at this:
http://codepen.io/anon/pen/egeOyJ
const userService = _.debounce(username => {
setTimeout(
()=>{
console.log('userService called after debounce. username:', username)
}
,1000)
}, 500)
const uniqueUsernameCheck = (username) => (dispatch) => {
console.log('I can see this')
userService(username)
}
console.log('begin')
const reducers = (action) => {console.log(action)}
const store = Redux.createStore(
reducers,
{},
Redux.applyMiddleware(ReduxThunk.default))
store.dispatch(uniqueUsernameCheck('rafael'))
store.dispatch(uniqueUsernameCheck('rafael'))
store.dispatch(uniqueUsernameCheck('rafael'))