I have the following code which check the user_id if available and then log me in but it logs me in only if I refresh the app. Any idea how to make this happen without this?
This is the order of functions:
First when you click the login button from Login.js:
<TouchableOpacity onPress={handleSubmit(_signIn)} style={{margin: 10, alignItems: 'center'}}>
then _signIn function which is in Login.js
_signIn = (values, dispatch) => {
const email = values.email;
const password = values.password;
dispatch(loginUser(email, password));
}
Now we dispatched email and password to loginUser from authActions.js
export function loginUser(email, password) {
return function (dispatch) {
return axios.post(SIGNIN_URL, { email, password }).then((response) => {
var { user_id, token } = response.data;
onSignIn(user_id); // Here I pass 'user_id' to onSignIn function
}).catch((error) => {
dispatch(addAlert("Could not log in."));
});
};
}
Now we get the user_id from loginUser inside Auth.js
import { AsyncStorage } from "react-native";
const USER_KEY = "auth_key";
export const onSignIn = (user_id) => AsyncStorage.setItem(USER_KEY, user_id);
export const onSignOut = () => AsyncStorage.removeItem(USER_KEY);
export const isSignedIn = () => {
return new Promise((resolve, reject) => {
AsyncStorage.getItem(USER_KEY)
.then(res => {
if (res !== null) {
resolve(true);
} else {
resolve(false);
}
})
.catch(err => reject(err));
});
};
Now in App.js I am calling the function isSignedIn to check if user_id is available and if so will choose which screen to show
constructor(props) {
super(props);
this.state = {
signedIn: false,
checkedSignIn: false
};
}
componentDidMount() {
isSignedIn()
.then(res => this.setState({ signedIn: res, checkedSignIn: true }))
.catch(err => alert("An error occurred"));
}
render() {
const { checkedSignIn, signedIn } = this.state;
// If we haven't checked AsyncStorage yet, don't render anything (better ways to do this)
if (!checkedSignIn) {
return null;
}
const Layout = createRootNavigator(signedIn);
It`s not async function callback issue - I do know how to use it, coz you are already use it in the isSignedIn function.
You did called onSignIn(userId), but you inform nobody about it. Those function, that calls isSignedIn should somehow know about a user logged in.
Based on this issue and previous one, I guess you should choose an architecture of your app (redux or just functional programming or something else) and keep it in mind.
If you wanna use redux, you should dispatch an action about a user logged in and reflect to state change where you need it.
Related
I am trying to send my variable 'backEndResponse' with its value from my Express.js backend to my React.js Frontend. I am not quite sure how to send a variable from the backend to the frontend. I have searched around and can't find any good resources. I would appreciate any help.
Express.js Backend
function getcookie(req) {
var authCookie = req.headers.cookie;
if (authCookie = req.headers.cookie) {
try {
return authCookie
.split('; ')
.find(row => row.startsWith('Auth='))
.split('=')[1];
} finally {
if (authCookie = result) {
backEndResponse = true
console.log(backEndResponse);
console.log(result);
} else {
backEndResponse = false
console.log(backEndResponse);
console.log(result);
}
}
} else {
}
}
app.get('/auth', (req, res) => {
getcookie(req)
if (backEndResponse) {
res.json(backEndResponse); // OR json({ message: "Authorised" })
} else {
res.json(backEndResponse); // OR json({ message: "Unauthorised" })
}
});
Frontend React.js
const useAuth = () => {
const [data, setData] = useState();
useEffect(() => {
const fetchAuthData = () => {
const result = axios('http://localhost:5000/auth');
console.log(result)
setData(result.data);
};
fetchAuthData()
}, []);
// Logic to check if backEndResponse is true or false
if (data) {
const authorized = {loggedIn: true}
return authorized && authorized.loggedIn;
} else {
const authorized = {loggedIn: false}
return authorized && authorized.loggedIn;
}
}
const ProtectedRoutes = () => {
const isAuth = useAuth();
return isAuth ? <Outlet/> : <Navigate to="/login" />;
}
You won't be able to send a variable directly, rather you will send a payload in a certain shape that best represents the data suited to the applications needs. To send a response payload in an express route use something like the following:
app.get('/auth', (req, res) => {
// do some logic for `backEndResponse`...
res.json(backEndResponse);
});
If you were intending to provide more information in the response such as HTTP headers differing based on the of backEndResponse then you might consider:
app.get('/auth', (req, res) => {
// do some logic for `backEndResponse`...
// send HTTP Ok if true, otherwise Bad Request
// consider handling 400 and/or 500 errors too
if (backEndResponse) {
res.status(200).json(true); // OR json({ message: "Authorised" })
} else {
res.status(401).json(false); // OR json({ message: "Unauthorised" })
}
});
A component fetching the above endpoint would be similar to:
const MyComponent = () => {
const [data, setData] = useState();
useEffect(() => {
const fetchAuthData = async () => {
const result = await axios('http://localhost:5000/auth');
setData(result.data); // true/false OR { message: "Authorised" }
};
fetchAuthData();
}, []);
// display payload
return (<div>{JSON.stringify(data)}</div>)
}
There is an opportunity to refactor the above into a custom hook should you find the need to reuse the functionality across multiple components.
axios request is async function, so you should do like that,
const useAuth = async () => {
try {
const res = await axios.get('http://localhost:5000/auth', {
withCredentials: true
})
return true
} catch (e) {
return false
}
};
I need your help.
I have an app. I can login with normal (email and password) and using Google Auth.
When i print my informations to console.log, it successfully prints, but I use firebase and i need to firebase.auth().onStateChanged for control the state.
For example, if i login with email and password, then onstateChanged function works with useEffect, and it redirect homepage (because this function know user has signed in), but when i login with google, i can't inform about user signed in or not because of google.
How can i use both google sign in and firebase? How can i tell firebase, i login with google, there are my informations don't worry.
Here google logs (my google name and googleId):
my codes:
google codes:
const GoogleAuth = () => {
const saveFirebase = (email, uid) => {
return firebase.firestore().collection("users").doc(uid).set({
email: email,
});
};
const onSuccess = (response) => {
const name = response.profileObj.name;
const uid = response.profileObj.googleId;
console.log(uid);
console.log(name);
return saveFirebase(name, uid);
};
const handleClick = () => {
return (
<GoogleLogin
clientId="1093050194946-tcn6k22l190klav7cat182leq09luthu.apps.googleusercontent.com"
buttonText="Login"
onSuccess={onSuccess}
onFailure={onSuccess}
cookiePolicy={"single_host_origin"}
theme="dark"
/>
);
};
return <div className="google-button">{handleClick()}</div>;
};
my constant variables:
const [user, setUser] = useState("");
onAuthStateChanged codes with useEffect:
const authListener = () => {
auth.onAuthStateChanged((user) => {
if (user) {
setUser(user);
setUsername(user.displayName);
} else {
}
});
};
useEffect(() => {
authListener();
});
To check if the user loged in with email or Google just use the providerId from user you get from the onAuthStateChanged like here:
const authListener = () => {
auth.onAuthStateChanged((user) => {
if (user) {
setUser(user);
setUsername(user.displayName);
//Here you can se what provider the user used
console.log('provider',user.providerId)
} else {
}
});
};
I have a function "sendMessage" in React class:
class MessageForm extends React.Component {
...
sendMessage = async () => {
const { message } = this.state;
if (message) {
this.setState({ loading: true });
if (this.props.isPrivateChannel === false) {
socket.emit("createMessage", this.createMessage(), (response) => {
this.setState({ loading: false, message: "", errors: [] });
});
} else {
if (this.state.channel && this.state.channel._id === undefined) {
socket.emit("createChannelPM", this.state.channel, async (response) => {
const chInfo = { ...response, name: this.props.currentChannel.name };
console.log("chInfo : ", chInfo);
await this.props.setCurrentChannel(chInfo).then((data) => {
if (data) {
console.log("data : ", data);
console.log("this.props.currentChannel : ", this.props.currentChannel);
}
});
});
}
...
function mapStateToProps(state) {
return {
isPrivateChannel: state.channel.isPrivateChannel,
currentChannel: state.channel.currentChannel,
};
}
const mapDispatchToProps = (dispatch) => {
return {
setCurrentChannel: async (channel) => await dispatch(setCurrentChannel(channel)),
}
};
Here, in sendMessage function, I retrieve "response" from socket.io, then put this data into variable "chInfo" and assign this to Redux state, then print it right after assinging it.
And Redux Action function, "setCurrentChannel" looks like:
export const setCurrentChannel = channel => {
return {
type: SET_CURRENT_CHANNEL,
payload: {
currentChannel: channel
}
};
};
Reducer "SET_CURRENT_CHANNEL" looks like:
export default function (state = initialState, action) {
switch (action.type) {
case SET_CURRENT_CHANNEL:
return {
...state,
currentChannel: action.payload.currentChannel
};
...
The backend Socket.io part look like (I use MongoDB):
socket.on('createChannelPM', async (data, callback) => {
const channel = await PrivateChannel.create({
...data
});
callback(channel)
});
The console.log says:
Problem : The last output, "this.props.currentChannel" should be same as the first output "chInfo", but it is different and only print out previous value.
However, in Redux chrome extension, "this.props.currentChannel" is exactly same as "chInfo":
How can I get and use newly changed Redux states immediately after assinging it to Redux State?
You won't get the updated values immediately in this.props.currentChannel. After the redux store is updated mapStateToProps of MessageForm component is called again. Here the state state.channel.currentChannel will be mapped to currentChannel. In this component you get the updated props which will be accessed as this.props.currentChannel.
I believe you want to render UI with the latest data which you which you can do.
I am using React+Redux+Redux Thunk + Firebase authentication. Writing code in Typescript.
My action is:
//Type for redux-thunk. return type for rdux-thunk action creators
type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
IStoreState, //my store state
null,
Action<userActionTypes>
>
export const signInWithEmailAndPasword =(email:string, pasword:string): AppThunk=>{
return async (dispatch)=>{
auth.signInWithEmailAndPassword(email, pasword).then(response=>{
if(response.user){
const docRef = db.collection("users").doc(response.user.uid);
docRef.get().then(function(doc) {
if (doc.exists) {
const userData = doc.data(); //user data from firebase DB
//if user exists in DB, dispatch
dispatch({
type: userActionTypes.SIGN_IN_USER,
payload: userData
})
return userData;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
}
})
.catch(err=> dispatch(setUserError(err.message)))
}
}
My SignIn component, where i am dispatching this above action:
import React, { useState } from 'react'
//some other imports
//...
//
import { useDispatch, useSelector } from 'react-redux';
import { signInWithEmailAndPasword } from '../../redux/actions/userActions';
interface ISignInState {
email: string;
password: string;
}
const SignIn = (props:any) => {
const [values, setValues] = useState<ISignInState>({ email: '', password: '' })
const dispatch = useDispatch();
const handleInputChange = (e: React.FormEvent<HTMLInputElement>): void => {
const { name, value } = e.currentTarget;
setValues({ ...values, [name]: value })
}
const handleFormSubmit = (e: React.FormEvent) => {
e.preventDefault()
const { email, password } = values;
dispatch(signInWithEmailAndPasword(email, password))
//// -> gives error: Property 'then' does not exist on
//// type 'ThunkAction<void, IStoreState, null, Action<userActionTypes>>'
.then(()=>{
props.history.push('/');
setValues({ email: '', password: '' })
})
}
return (<div>Sign in UI JSX stuff</div>)
So when i try to use .then() after dispatch(signInWithEmailAndPasword(email, password)) it gives an error Property 'then' does not exist on type 'ThunkAction<void, IStoreState, null, Action<userActionTypes>>'
So how can i return promise from redux action and chain a .then() on it? I always assumed that thunk actions return promises by default.
Thanks for your help
Edit:
Temporary soluton was to use any as return type of above action:
export const signInWithEmailAndPasword = (email:string, pasword:string):any =>{
return async (dispatch: any)=>{
try {
const response = await auth.signInWithEmailAndPassword(email, pasword)
if(response.user){
const userInDb = await getUserFromDB(response.user)
dispatch(userSignIn(userInDb))
return userInDb
}
} catch (error) {
dispatch(setUserError(error.message))
}
}
}
But I don't want to use any
Just add return before this line:
auth.signInWithEmailAndPassword(email, pasword).then(response=>{
So it would be:
export const signInWithEmailAndPasword =(email:string, pasword:string): AppThunk=>{
return async (dispatch)=>{
return auth.signInWithEmailAndPassword(email, pasword).then(response=>{
It should work.
AppThunk<Promise<void>>
You need to explicitly declare the AppThunks return type, which in this case should be a Promise containing nothing. You have already made it async so just make sure to enter the correct AppThunk return type
export const signInWithEmailAndPassword = (email: string, password: string): AppThunk<Promise<void>> => {
return async (dispatch) => {
// do stuff
}
}
Thunks return functions, not promises. For this you could look at redux-promise. But to be honest if your doing something this complex you would be much better off using redux-saga.
Another approach would be to use the concepts behind redux-api-middleware to create your own custom redux middleware. I have done this in the past to connect a message queue to redux.
I have a function which uses Firebase auth to update a user's email:
export const updateEmail = async (email) => {
const user = auth.currentUser;
return user.updateEmail(email);
};
It is used in a function which gets an email from a form (in React) and tries to update the email. If there is an error, we change the state to reflect that.
handleSave = (e) => {
const email = e.target.email.value;
updateEmail(email).catch((err) => {
this.setState({ didError: true, emailError: err.message });
});
};
However, when an error occurs, in the console I get:
My question is: why does this still say 'Uncaught'? Does the .catch() in handleSave not take care of that?
update
Link to relevant Firebase docs
Assuming updateEmail returns a prmise, I guess you can try:
export const updateEmail = (email) => { // no need for async here
const user = auth.currentUser;
return user.updateEmail(email);
};
handleSave = async (e) => {
const email = e.target.email.value;
try{
await updateEmail(email);
}catch(err){
this.setState({ didError: true, emailError: err.message });
}
};
I'm not quite sure since I don't know so much about Firebase, let me suggest something.
export const updateEmail = async (email) => {
const user = auth.currentUser;
const response = await user.updateEmail(email);
if ( response.error ) {
throw new Error( response.error );
}
return "something else";
};