React Native logout functionality not working properly - javascript

Hy, I'm creating the react-native app in native-cli. I'm trying first-time navigation 5. when I do the login I receive the Token and store it in AsyncStorage but need to reload the app to move toward the dashboard so to solve this I used the useContext and its working fine but now the issue is that when I login the app and move around it and then press the logout button it works nicely but when I login the app surf the app and then press the back button go to the home screen of mobile without logout then again when I come back to the app and press log out it clear the AsyncStorge but not log out the app and I need to refresh then it goes back to the login screen.
App.js
const App = () => {
const [user, setUser] = useState(false)
const [log, setLog] = useState(0)
const [role, setRole] = useState('seller')
//console.log('App.js-Start')
console.log("app_User:",user);
console.log("app_log:",log);
useEffect(()=>{
getKeysData(dataKeys)
},[])
const dataKeys = ['token', 'super_user_status', 'isLoggedIn'];
const getKeysData = async (keys) => {
const stores = await AsyncStorage.multiGet(keys);
//console.log(stores)
// const aData = stores.map(([key, value]) => ({ [key]: value }))
const aData = await Promise.all(stores.map(([key, value]) => ({[key]: value})))
const token = aData[0]['token']
const super_user_status = aData[1]['super_user_status']
const isLoggedIn = aData[2]['isLoggedIn']
console.log('token',token)
console.log('SuperUser', super_user_status)
console.log('Log',isLoggedIn)
//setUser(isLoggedIn)
if(isLoggedIn == 1){
setLog(1)
}
}
return (
<NavigationContainer>
<LoginContext.Provider value={{user,setUser}} >
{ user == false && log==0 ?
<AuthStackScreen />
:
<BuyerDashboardStackScreens />
}
</LoginContext.Provider>
</NavigationContainer>
);
};
export default App;
Login
await axios({
method: 'POST',
url: api + 'login/',
data: login_Credentials,
headers: { 'Content-Type': 'multipart/form-data' }
}).then(async function (response) {
if (response.data.success == true) {
const token = response.data.token.toString();
const super_user_status = response.data.super_user_status.toString();
const isLoggedIn = "1"
//console.log('Logged In and set Storgae')
await AsyncStorage.multiSet([['isLoggedIn',isLoggedIn],['token', token], ['super_user_status', super_user_status]])
setUser(true)
setEmail('')
setPassword('')
setPress(false)
}
logout
const logOut = () => {
AsyncStorage.clear();
setUser(false);
};

Related

React.js, Auth Component does not redirect properly

I have created this Auth Component and it works fine. Except that, It does not redirect if the unauthenticated user tries to visit /dashboard.
The backend upon receiving /api/me request, knows the user by having the cookie. So I have (Cookie-Session) Authentication technique.
export const UserContext = createContext();
const Auth = ({ children }) => {
const [user, setUser] = useState(null);
const [gotUser, setGotUser] = useState(false);
const navigate = useNavigate();
const getUser = async () => {
const res = await fetch('/api/me');
const data = await res.json();
setUser(data);
if (user) {
setGotUser(true);
}
};
useEffect(() => {
if (!gotUser) {
getUser();
}
}, [user, gotUser, navigate]);
if (!user) {
navigate('/login');
}
console.log(user);
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
So the main issue is that no redirection done. Also, The user passed to the context is not updated properly. Maybe because I am confused about what to use in useEffect .
Any help is appreciated.
Issues
There are a couple issues:
The "unauthenticated" state matches the "I don't know yet" state (i.e. the initial state value) and the component is redirecting too early. It should wait until the user state is confirmed.
The navigate function is called as an unintentional side-effect directly in the body of the component. Either move the navigate call into a useEffect hook or render the Navigate component to issue the imperative navigation action.
Solution
Use an undefined initial user state and explicitly check that prior to issuing navigation action or rendering the UserContext.Provider component.
const Auth = ({ children }) => {
const [user, setUser] = useState(); // <-- initially undefined
const navigate = useNavigate();
const getUser = async () => {
try {
const res = await fetch('/api/me');
const data = await res.json();
setUser(data); // <-- ensure defined, i.e. user object or null value
} catch (error) {
// handler error, set error state, etc...
setUser(null); // <-- set to null for no user
}
};
useEffect(() => {
if (user === undefined) {
getUser();
}
}, [user]);
if (user === undefined) {
return null; // <-- or loading indicator, spinner, etc
}
// No either redirect to log user in or render context provider and app
return user
? <Navigate to="/login" replace />
: <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
useEffect runs after your JSX is rendered, so as your code is made, on a page refresh this if (!user) that calls navigate('/login') will always pass, as before the useEffect does its work, user is null, that inital value you gave to useState. Yet it's not redirecting because navigate does not work inside JSX, it should be replaced with Navigate the component.
Also, in getUser, you have this if (user) juste after setUser(data), that wouldn't work well as user won't get updated immediately, as updating a state is an asynchronous task which takes effect after a re-redner .
To fix your problems you can add a checking state, return some loader while the user is being verified. Also you can optimise a little bit your code overall, like getting ride of that gotUser state:
export const UserContext = createContext();
const Auth = ({ children }) => {
const [user, setUser] = useState(null);
const [checking, setChecking] = useState(true);
const getUser = async () => {
try {
const res = await fetch("/api/me");
const data = await res.json();
setUser(data);
} catch (error) {
setUser(null);
} finally {
setChecking(false);
}
};
useEffect(() => {
if (!user) {
getUser();
}
}, [user]);
if (checking) {
return <p>Checking...</p>;
}
if (!user) {
return <Navigate to="/login" replace />
}
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
};
export default Auth;

Search function now working in React photo gallary

Working on a small application that takes a pexels api and displays photos dynamically. When I send the search request for my api to fectch based on the new params, it does actually update the page with new photos but not the ones based on the params. I though I got the search function correct, maybe it's cause I'm not using it in a useEffect? But if I did use it in a useEffect, I wouldn't be able to set it on the onClick handle. I tried to console.log the query I was getting from the onChange but it doesn't seem like it's getting the result. What am I doing wrong?
import { useState, useEffect } from 'react'
import pexelsApi from './components/pexelsApi'
import './App.css'
const App = () => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(false);
const [nextPage, setNextPage] = useState(1);
const [perPage, setPerPage] = useState(25);
const [query, setQuery] = useState('');
const [error, setError] = useState('');
useEffect(() => {
const getImages = async () => {
setLoading(true);
await pexelsApi.get(`/v1/curated?page=${nextPage}&per_page=${perPage}`)
.then(res => {
setImages([...images, ...res.data.photos]);
setLoading(false);
}).catch(er => {
if (er.response) {
const error = er.response.status === 404 ? 'Page not found' : 'Something wrong has happened';
setError(error);
setLoading(false);
console.log(error);
}
});
}
getImages();
}, [nextPage, perPage]);
const handleLoadMoreClick = () => setNextPage(nextPage + 1)
const search = async (query) => {
setLoading(true);
await pexelsApi.get(`/v1/search?query=${query}&per_page=${perPage}`)
.then(res => {
setImages([...res.data.photos]);
console.log(res.data)
setLoading(false);
console.log(query)
})
}
if (!images) {
return <div>Loading</div>
}
return (
<>
<div>
<input type='text' onChange={(event) => setQuery(event.target.value)} />
<button onClick={search}>Search</button>
</div>
<div className='image-grid'>
{images.map((image) => <img key={image.id} src={image.src.original} alt={image.alt} />)}
</div>
<div className='load'>
{nextPage && <button onClick={handleLoadMoreClick}>Load More Photos</button>}
</div>
</>
)
};
export default App
import axios from 'axios';
export default axios.create({
baseURL: `https://api.pexels.com`,
headers: {
Authorization: process.env.REACT_APP_API_KEY
}
});
Your main issue is that you've set query as an argument to your search function but never pass anything. You can just remove the arg to have it use the query state instead but you'll then need to handle pagination...
// Helper functions
const getCuratedImages = () =>
pexelsApi.get("/v1/curated", {
params: {
page: nextPage,
per_page: perPage
}
}).then(r => r.data.photos)
const getSearchImages = (page = nextPage) =>
pexelsApi.get("/v1/search", {
params: {
query,
page,
per_page: perPage
}
}).then(r => r.data.photos)
// initial render effect
useEffect(() => {
setLoading(true)
getCuratedImages().then(photos => {
setImages(photos)
setLoading(false)
})
}, [])
// search onClick handler
const search = async () => {
setNextPage(1)
setLoading(true)
setImages(await getSearchImages(1)) // directly load page 1
setLoading(false)
}
// handle pagination parameter changes
useEffect(() => {
// only action for subsequent pages
if (nextPage > 1) {
setLoading(true)
const promise = query
? getSearchImages()
: getCuratedImages()
promise.then(photos => {
setImages([...images, ...photos])
setLoading(false)
})
}
}, [ nextPage ])
The reason I'm passing in page = 1 in the search function is because the setNextPage(1) won't have completed for that first page load.

React Native: How to access a variable from parent component in child component?

i am trying to pass my newsocket variable from my MessagesScreen.js to my ChatScreen.js.
I have been stuck on this point for a while and would appreciate any help possible. What i am trying to achieve is that only one connection gets emitted which i can listen to events on both screen.
The connection is now opened on the messagesScreen. My problem now is if user 1 is on the allmessages screen and user 2 is inside the chat. And user 2 sends user 1 a message, user 1's screen does not automatically update with the last message for the conversation the message was sent to, I need to either scroll to refresh or navigate from one page to the other in order for it to appear.
Here is my code:
PARENT --> messagesScreen.js
function MessagesScreen({navigation}) {
const [posts, setPosts] = useState([]);
const { user } = useAuth();
const [socket, setSocket] = useState(null);
const loadposts = async () => {
const response = await messagesApi.getMessages();// here i am loading all the conversation this user has
setPosts(response.data)
};
useEffect(() => {
newsocket = sockets(user.id); // newsocket is what i am trying to pass to child component
setSocket(newsocket);
loadPosts()
newsocket.on("send_message", (msg) => {
console.log("messages:", msg);
})
}, []);
return (
<FlatList
data={posts}
keyExtractor={(post) => post.id.toString()}
renderItem={({ item,index }) => (
<MessagesList
title={item.Post.title}
subTitle={item.Messages[0].message}
onPress={() => navigation.navigate(
routes.CHAT,{message:item,index,newsocket:socket})}
/>
)}
/>
)
CHILD ---> chatScreen.js
function ChatScreen({route,navigation,socket}) {
const [messages, setMessages] = useState([]);
const { user } = useAuth();
const index = route.params.index;
const message = route.params.message;
const newsocket = route.params.newsocket;
const loadListings = async () => {
const response = await messagesApi.getConversation(message.id);// here i am loading the messages in that specific conversation
setMessages(response.data.Messages)
};
useEffect(() => {
loadListings()
newsocket.emit('subscribe', message.id);
newsocket.on("send_message", (msg) => {
console.log("this is the chat messages:", msg);
setMessages(messages => [msg, ...messages]);
});
}, []);
const onSend = (ConversationId,senderId,receiverId,message) => {
const to = (user.id===route.params.message.user1?
route.params.message.user2:route.params.message.user1)
socket.emit('message', { to: to, from: user.id, message,ConversationId});
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
};
return(
<FlatList
inverted
data={messages}
keyExtractor={(item,index)=>index.toString()}
extraData={messages} // add this
renderItem={({item,index})=>(
<MessageBubble
text={item.message}
mine={item.senderId !== user.id}
/>
)}
/>
)
socket.js
import io from 'socket.io-client';
const newsocket = (user) => {
let newsocket = io.connect("http://192.168.1.107:9000")
newsocket.on('connect', msg => {
console.log(`waiting for user: ${user} to join a conversation`)
});
newsocket.emit('waiting', user);
return newsocket;
}
export default newsocket;
I would approach this differently.
You can create your socket connection as a shared service in a separate module and simply import that into the relevant components you need. In this shared module you handle connecting/disconnecting and return an existing connection or create a new connection to return.
Quick rough:
// socket-server.ts
import io from 'socket.io-client';
let socket: SocketIOClient.Socket = null;
export const getSocketServer = (): Promise<SocketIOClient.Socket> => {
return new Promise<SocketIOClient.Socket>(resolve => {
if (socket) {
console.info(`returning existing socket: ${socket.id}`);
return resolve(socket);
}
socket = io('http://localhost:4000', {
autoConnect: false,
});
socket.on('connect_error', (err) => {
console.error(err);
})
socket.on('connect', () => {
console.info(`creating new socket: ${socket.id}`);
return resolve(socket);
});
socket.open();
})
}
// then in your relevant modules
// module-a.ts
import React, {useEffect, useState} from 'react';
import {getSocketServer} from './../components/socket-server';
const Component = () => {
useEffect(() => {
const connect = async () => {
const socket = await getSocketServer();
socket.on('hello', (message) => {
console.info('hello from module A', message);
});
}
connect();
}, []);
return (
<>
<h2>Module A</h2>
</>
)
}
export default Component;
You could maybe also look at creating a Context Provider and share the socket with relevant modules as needed.
Context provides a way to pass data through the component tree without
having to pass props down manually at every level.
On the MessagesScreen screen you are passing the SOCKET function and not the variable it self . i think you do not need the function . you directly pass the variable and access in chatScreen screen .
MessagesScreen.js
routes.CHAT,{message:item,index,updateView, newsocket})}
chatScreen.js
const newsocket = route.params.newsocket;
....
newsocket.emit('subscribe', message.id); // call like this

Ionic-React Accessing Local Storage For Mobile

I am trying to create a login simple login system using ionic and a database. I'm using a simple SQL Query to make sure the login credentials are valid, and then shoot the user over to the main dashboard page. I am trying to display the name of the user with a header 'Welcome '.
I am using Ionic with react.js. I am also using Capacitor. Whenever I test the app on my laptop as a web app, works like intended and shows the user's name. As soon as I test if via my android device, it logs me in correctly but does not show the user's name. What could I be missing?
For debugging purposes I changed it from the name to the id number of the user... still not properly displaying the number tho...
Web app - This is what the mobile is supposed to look like but isn't
Login Code
import React, { useState, useEffect } from 'react';
import { Plugins } from '#capacitor/core';
import './Login.css';
import { Link, Redirect } from 'react-router-dom';
import axios from 'axios';
const { Storage } = Plugins;
const Login: React.FC = () => {
const [username, setUsername] = useState<string>('');
const [password, setPassword] = useState<string>('');
const [isError, setIsError] = useState<boolean>(false);
function handleLogin() {
// // const baseURL = process.env.NODE_ENV !== "production" ? "http://localhost:3000" : "https://freightsnap-proto.herokuapp.com"
const baseURL = "https://freightsnap-proto.herokuapp.com";
console.log("user: " + username);
console.log("pass: " + password);
let userInfo = {
username: username,
password: password
}
axios.post(baseURL + "/login", userInfo) .then(function(response) {
if(response.data.length != 0) {
setIsError(false);
let userInfo = response.data[0];
let data = JSON.stringify(userInfo);
setUserData(data, userInfo.id);
}
else {
console.log("err");
setIsError(true);
}
});
}
async function setUserData(data: any, id: any){
await Storage.set({
key: 'user',
value: data,
});
await Storage.set({
key: '_id',
value: id,
});
window.location.href = "/home"
// getUserData();
}
async function getUserData() {
// const { value } = await Storage.get({ key: 'user' });
// console.log("getting...");
// const user = console.log(value);
// const ret = await Storage.get({ key: '_id' });
// const user = JSON.parse(ret.value || '{}');
// console.log(user);
}
return (
<IonPage>
<IonContent>
<div className="bg-light">
<h1 className="header">[LOGO]</h1>
<div className="container">
<IonInput style={{ paddingTop: "30px" }} placeholder="Username" className="dark-txtbox" value={username} onIonChange={(e: any) => setUsername(e.target.value)} ></IonInput>
<IonInput placeholder="Password" type="password" className="dark-txtbox" value={password} onIonChange={(e: any) => setPassword(e.target.value)} ></IonInput>
<IonButton onClick={handleLogin} className="btn-mainBlue" shape="round" expand="full">Login</IonButton>
{
isError ? (
<p style={{color: "red"}}>Invalid Login!</p>
) : (
<p></p>
)
}
</div>
</div>
</IonContent>
</IonPage>
);
};
export default Login;
Dashboard Code
import React, { useState, useEffect } from 'react';
import ExploreContainer from '../components/ExploreContainer';
import { Plugins } from '#capacitor/core';
import './Home.css';
import axios from 'axios';
const { Storage } = Plugins;
const Home: React.FC = () => {
const [userFullName, setUserFullName] = useState<string>('');
useEffect(() => {
getUserData();
// const baseURL = process.env.NODE_ENV !== "production" ? "http://localhost:3000" : "https://freightsnap-proto.herokuapp.com"
// var url = window.location.href;
// var splitUrl = url.split("/");
// var userId = splitUrl[4]
// console.log(userId);
// axios.get(baseURL + `/findUser/${userId}`).then(response => {
// setUserFullName(response.data[0].user_name);
// console.log(userFullName);
// })
})
async function getUserData() {
const { value } = await Storage.get({ key: '_id' });
// const baseURL = process.env.NODE_ENV !== "production" ? "http://localhost:3000" : "https://freightsnap-proto.herokuapp.com"
const baseURL = "https://freightsnap-proto.herokuapp.com";
console.log(value);
setUserFullName(value || "");
// axios.get(baseURL + `/findUser/${value}`).then(response => {
// setUserFullName(response.data[0].user_name);
// console.log(userFullName);
// })
}
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Dashboard - Welcome {userFullName}</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">Blank</IonTitle>
</IonToolbar>
</IonHeader>
<ExploreContainer />
</IonContent>
</IonPage>
);
};
export default Home;
Based on your code, I think your async function getUserData in Dashboard code is still retrieving the data from your server, so the data is not available yet to show. A suggested change that might work will be
useEffect(() => {
getUserData();
}, [userFullName]);
This will update userFullName when there is a change in that variable and should update the view with the name of the user.

Using the Context API gives me undefined

So I'm using Auth0 for my user sign up. I'm trying to get the user id under sub:value to add to my database to identify with the post of a user. I'm trying to use a Context API in order to get the user info to put in my database.
react-auth0-spa.js
// src/react-auth0-spa.js
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
if (window.location.search.includes("code=") &&
window.location.search.includes("state=")) {
const { appState } = await auth0FromHook.handleRedirectCallback();
onRedirectCallback(appState);
}
const isAuthenticated = await auth0FromHook.isAuthenticated();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const user = await auth0FromHook.getUser();
setUser(user);
}
setLoading(false);
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await auth0Client.getUser();
setUser(user);
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await auth0Client.getUser();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
other.js (trying to get user info from react-auth0-spa.js)
class AddAlbum extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
let value = this.context;
console.log(value);
}
render() {
return (
)
}
AddAlbum.contextType = Auth0Context;
This gives me user: undefined
In my index.js I have this
ReactDOM.render(
<Auth0Provider
domain={config.domain}
client_id={config.clientId}
redirect_uri={window.location.origin}
onRedirectCallback={onRedirectCallback}
>
<App />
</Auth0Provider>,
document.getElementById("root")
);
Which I believe is giving me these results:
So I'm wondering why my Context API isn't working and giving me user: undefined.
You're logging the user when the component first mounts, which is long before the await auth0FromHook.getUser() call will complete. Log it in a componentDidUpdate, or check in a parent if that value is available, and don't mount the child component until it is.

Categories

Resources