AuthContext data update issue with react native - reactjs

I created a Context object named AuthContext to hold user information on the application. After I get the user information with this Context object and do the necessary operations, I save the information with the AsnycStore and direct it to the Main.js page. but sometimes I need to change this information. I created a file named API/index.js and wrote a function that can re-login according to the user's status. when I run this function it will need to trigger a function under the AuthContext I created but I can't call the function in the AuthContext
AuthContext.js
import AsyncStorage from '#react-native-async-storage/async-storage';
import React, { createContext, useEffect, useState } from 'react';
export const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
//const [test, setTest] = useState("test tuta");
const [userToken, setUserToken] = useState(null);
const [userInfo, setUserInfo] = useState(null);
const [isLoading, setIsLoading] = useState(null);
const [guest, setGuest] = useState(null)
const login = (userInfo) => {
setIsLoading(true);
setUserToken(userInfo.kullanici_id);
setUserInfo(userInfo);
AsyncStorage.setItem("userToken", JSON.stringify(userInfo.kullanici_id));
AsyncStorage.setItem("localUserInfo", JSON.stringify(userInfo));
setIsLoading(false)
}
const isGuest = () => {
setIsLoading(true);
setGuest(true);
AsyncStorage.setItem("guest", "true");
setIsLoading(false)
}
const test= ()=>{ //I will call this function in API/index.js
console.log("test log")
}
const logout = () => {
setIsLoading(true);
setUserToken(null);
setUserInfo(null);
setGuest(null);
AsyncStorage.removeItem("userToken");
AsyncStorage.removeItem("localUserInfo");
AsyncStorage.removeItem("guest")
setIsLoading(false)
}
const isLoggedIn = async () => {
try {
setIsLoading(true);
let userToken = await AsyncStorage.getItem("userToken");
setUserToken(userToken);
let userInfo = await AsyncStorage.getItem("localUserInfo");
setUserInfo(JSON.parse(userInfo));
console.log("------------- userlocal")
console.log(userInfo);
setIsLoading(false);
} catch (e) {
console.log("isLoggenIn error ${e}")
}
}
const isGuestIn = async () => {
try {
setIsLoading(true);
let guestToken = await AsyncStorage.getItem("guest");
setGuest(guestToken);
setIsLoading(false);
} catch (e) {
console.log("isLoggenIn error ${e}")
}
}
useEffect(() => {
isLoggedIn(),
isGuestIn()
}, [])
return (
<AuthContext.Provider value={{ login, logout, isLoading, userToken, guest, isGuest,userInfo,deneme }}>
{children}
</AuthContext.Provider>
)
}
API/index.js
import AsyncStorage from "#react-native-async-storage/async-storage";
import axios from "axios";
import { useContext } from "react";
import { BASE_URL } from "../config";
import { AuthContext,AuthProvider } from "../context/AuthContext";
export const oturumKontrol = async () => {
const { test} = useContext(AuthContext);
test(); //Im using test function inside AuthContext
console.log("oturum kontrol")
}
Error
Possible Unhandled Promise Rejection (id: 0):
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
How can I access the function inside the AuthContext?

You cannot use hooks in normal functions. They must be used in functional components at top level.
You can pass the function from the hook as an argument to another function and use it like that.
//SomeComponent where you want to call oturumKontrol
const { test} = useContext(AuthContext); // get function
//call this on press or in useEffect
const handleTest = async () => {
await oturumKontrol(test) //pass function
}
export const oturumKontrol = async (test) => {
test(); //call function
console.log("oturum kontrol")
}
Make sure that you pass test function in your AuthContext first

Related

UseContext(Context) throwing an invalid hook call error

For some reason, logging out using Context doesn't throw any error. Yet when it comes to logging in I get an invalid hook call error upon submitting my sign in form.
this is the context code:
import { createContext, useState, useEffect, useContext } from "react";
import supabase from "../lib/supabase";
import { useRouter } from 'next/router';
const Context = createContext();
const Provider = ({ children }) => {
const router = useRouter();
const [user, setUser] = useState(supabase.auth.user())
useEffect(() => {
supabase.auth.onAuthStateChange(() => {
setUser(supabase.auth.user())
})
}, []);
const login = async () => {
try{
setLoading(true)
const { error } = await supabase.auth.signIn({email, password});
if (error) throw error
} catch (error) {
alert(error.error_description || error.message)
} finally {
setLoading(false);
}
}
const logout = async () => {
await supabase.auth.signOut();
setUser(null);
router.push('/app');
};
const exposed = {
user,
login,
logout,
}
return <Context.Provider value={exposed}>{children}</Context.Provider>;
};
export const useUser = () => useContext(Context);
export default Provider;
From the research I've done trying to solve it, it may be a problem with exporting and not having the useUser in a functional component, though I'm not sure why it's working for one and not the other.
and this is the error I get:
Unhandled Runtime Error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
43 | };
44 |
> 45 | export const useUser = () => useContext(Context);
| ^
46 |
47 | export default Provider;
48 |
and these are the login/logout pages:
// this works
const Logout = () => {
const { logout } = useUser();
useEffect(() => logout, []);
return <p>Logging out</p>;
};
// throws error
const handleLogin = async (email, password, setLoading) => {
const { login } = useUser();
useEffect(() => login, []);
}

How to get imported functions to set state of functional component?

I have a react class component with rather lengthy onSubmit function that I have put into another file in order to keep the code a bit tidier.
I tried to convert the class component to a functional one, replacing all of my state and setState functions with useState but now my useState state updaters are returning undefined inside the imported function. Am I able to update state using an imported function with a functional component? The function worked fine when it was imported into a class component and my state updater was setState();
//Imported function in utils.js
export const loginUser = async function (email, password) {
try {
const login = await axios.post('http://localhost:5000/api/v1/auth/login', {
email,
password
});
const options = {
headers: {
Authorization: `Bearer ${login.data.token}`
}
};
const getUser = await axios.get(
'http://localhost:5000/api/v1/auth/me',
options
);
const user = getUser.data.data;
setAuthenticated(true);
setUser(getUser.data.data);
setEmail('');
setPassword('');
localStorage.setItem('user', JSON.stringify(user));
console.log(localStorage.getItem('user'));
} catch (err) {
console.log(err);
}
};
// Functional component with imported function
import React, { useState } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import { Login } from './Login';
const { loginUser } = require('../utils/utils');
export const Splash = () => {
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
const [authenticated, setAuthenticated] = useState(false);
const [msg, setMsg] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const _handleEmail = (e) => {
setEmail(e.target.value);
};
const _handlePass = (e) => {
setPassword(e.target.value);
};
const _handleSubmit = async (e) => {
e.preventDefault();
loginUser(email, password);
if (user) {
console.log(user);
this.props.onHandleUser(user);
}
};
return (
<div className='splashStyle'>
{!authenticated && (
<Login
handleEmail={_handleEmail}
handlePass={_handlePass}
handleSubmit={_handleSubmit}
isAuthenticated={authenticated}
/>
)}
</div>
);
};d
EDIT: My issue that setAuthenticated, setUser, setEmail, and setPassword are coming undefined in utils.js
Thanks!
One way of achieving that would be passing all the set methods as a paramters to loginUser function.
But a better way of doing this will be like:
create two separate files
1 for login api call like :
login.js
function login(email, password){
const login = await axios.post('http://localhost:5000/api/v1/auth/login', {
email,
password
});
return login.data;
}
another for getting data
getProfile.js
function getProfile(token){
const options = {
headers: {
Authorization: `Bearer ${token}`
}
};
const getUser = await axios.get(
'http://localhost:5000/api/v1/auth/me',
options
);
return getUser.data;
}
Now do you setting state stuff in actuall component submit call function like
const _handleSubmit = async (e) => {
e.preventDefault();
const token = await login(email, password);
const user = await getProfile(token);
if (user) {
console.log(user);
props.onHandleUser(user);
setAuthenticated(true);
setUser(getUser.data.data);
setEmail('');
setPassword('');
localStorage.setItem('user', JSON.stringify(user));
console.log(localStorage.getItem('user'));
}
};
You need to pass the setAuthenticated function to the loginUser function before calling it in that.
return an onSubmiHandler function from your login user hook.
const doLogin = (email , password) => {
/// your code here
}
return {doLogin}
then use the doLogin function inside your main component
//inside functional component
const {doLogin} = loginUser();
onSubmit = () => doLogin(email, password)
for more you can see how to use custom hooks from here
https://reactjs.org/docs/hooks-custom.html
To start loginUser can't know about the setState you insert there try to pass it as arguments and it will fix it 😁
another problem I see is that you use the this keyword and in the functional component you use the just props.
and just for you to know don't pass null as the initial value pass an empty string, number, etc..
Update
this is how you also pass a setState as argument
loginUser((e)=>setEmail(e))

Invalid hook call React

https://reactjs.org/docs/hooks-custom.html
Building your own Hooks lets you extract component logic into reusable functions.
Thats what I want to do: extract my component logic into reusable functions for other components.
My functional component:
//React
import React from 'react';
import { FlatList, View, Text, StyleSheet } from 'react-native';
//Local
import UserPreview from './UserPreview';
import defaultContainer from '../../shared/styles/defaultContainer';
import useFetchUsers from './../../../handler/useFetchUsers';
export default function UserList(props) {
const { users } = props;
const dispatch = useDispatch();
//State
const [isLoading, setIsLoading] = React.useState(false);
const [error, setError] = React.useState(null);
return (
<View style={defaultContainer}>
<FlatList
data={users}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <UserPreview user={item} />}
ListEmptyComponent={() => <Text style={styles.listEmpty}>Keine Benutzer gefunden!</Text>}
ItemSeparatorComponent={() => <View style={styles.listSeperator} />}
onRefresh={useFetchUsers}
refreshing={isLoading}
contentContainerStyle={styles.container}
/>
</View>
);
}
my reusable function:
import React from 'react';
import * as userActions from '../store/actions/user';
import { useDispatch } from 'react-redux';
export default async function useFetchUsers() {
const [error, setError] = React.useState(null);
const dispatch = useDispatch();
const [isLoading, setIsLoading] = React.useState(false);
console.log('StartupScreen: User laden');
setIsLoading(true);
setError(null);
try {
await dispatch(userActions.fetchUsers());
console.log('StartupScreen: User erfolgreich geladen');
} catch (err) {
setError(err.message);
}
setIsLoading(false);
}
How should I use my function in the onRefresh prop in my Userlist?
I'm getting this error: Invalid hook call
You are using useFetchUsers as a callback. Rules of Hooks forbid this.
useFetchUsers should return some function that can be used as callback:
export default function useFetchUsers() {
const [error, setError] = React.useState(null);
const dispatch = useDispatch();
const [isLoading, setIsLoading] = React.useState(false);
return async function() {
console.log('StartupScreen: User laden');
setIsLoading(true);
setError(null);
try {
await dispatch(userActions.fetchUsers());
console.log('StartupScreen: User erfolgreich geladen');
} catch (err) {
setError(err.message);
}
setIsLoading(false);
}
}
function UserList(props) {
...
const handleRefresh = useFetchUsers();
...
return <FlatList onRefresh={handleRefresh} />;
}
React hooks can't be an async function. So according to this redux workflow:
You have to dispatch fetch user's action and then your loading and error states should be in your reducer and if you have any side effect manager such as redux-saga alongside your redux, you have to call all HTTP methods right there and your components just should dispatch and present the results. The other way is to call and fetch users into your hook and put them into your redux store via an action which you dispatch.
In this way, loading and error can be in your hook(in your local hook state, not into the redux-store).
So let's try this code(I've implemented the second way):
import React from 'react';
import * as userActions from '../store/actions/user';
import { useDispatch } from 'react-redux';
export default function useFetchUsers() {
const [error, setError] = React.useState(null);
const dispatch = useDispatch();
const [isLoading, setIsLoading] = React.useState(false);
React.useEffect(() => {
(async () => {
console.log('StartupScreen: User laden');
setIsLoading(true);
setError(null);
try {
const res = await fetchUsers();
dispatch(setUsers(res.data));
console.log('StartupScreen: User erfolgreich geladen');
setIsLoading(false);
} catch (err) {
setIsLoading(false);
setError(err.message);
}
})()
}, [])
}

React Hooks + Mobx => Invalid hook call. Hooks can only be called inside of the body of a function component

I have a React Native App,
Here i use mobx ("mobx-react": "^6.1.8") and react hooks.
i get the error:
Invalid hook call. Hooks can only be called inside of the body of a function component
Stores index.js
import { useContext } from "react";
import UserStore from "./UserStore";
import SettingsStore from "./SettingsStore";
const useStore = () => {
return {
UserStore: useContext(UserStore),
SettingsStore: useContext(SettingsStore),
};
};
export default useStore;
helper.js OLD
import React from "react";
import useStores from "../stores";
export const useLoadAsyncProfileDependencies = userID => {
const { ExamsStore, UserStore, CTAStore, AnswersStore } = useStores();
const [user, setUser] = useState({});
const [ctas, setCtas] = useState([]);
const [answers, setAnswers] = useState([]);
useEffect(() => {
if (userID) {
(async () => {
const user = await UserStore.initUser();
UserStore.user = user;
setUser(user);
})();
(async () => {
const ctas = await CTAStore.getAllCTAS(userID);
CTAStore.ctas = ctas;
setCtas(ctas);
})();
(async () => {
const answers = await AnswersStore.getAllAnswers(userID);
UserStore.user.answers = answers.items;
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
setAnswers(answers.items);
})();
}
}, [userID]);
};
Screen
import React, { useEffect, useState, useRef } from "react";
import {
View,
Dimensions,
SafeAreaView,
ScrollView,
StyleSheet
} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import { observer } from "mobx-react";
import useStores from "../../stores";
import { useLoadAsyncProfileDependencies } from "../../helper/app";
const windowWidth = Dimensions.get("window").width;
export default observer(({ navigation }) => {
const {
UserStore,
ExamsStore,
CTAStore,
InternetConnectionStore
} = useStores();
const scrollViewRef = useRef();
const [currentSlide, setCurrentSlide] = useState(0);
useEffect(() => {
if (InternetConnectionStore.isOffline) {
return;
}
Tracking.trackEvent("opensScreen", { name: "Challenges" });
useLoadAsyncProfileDependencies(UserStore.userID);
}, []);
React.useEffect(() => {
const unsubscribe = navigation.addListener("focus", () => {
CTAStore.popBadget(BadgetNames.ChallengesTab);
});
return unsubscribe;
}, [navigation]);
async function refresh() {
const user = await UserStore.initUser(); //wird das gebarucht?
useLoadAsyncProfileDependencies(UserStore.userID);
if (user) {
InternetConnectionStore.isOffline = false;
}
}
const name = UserStore.name;
return (
<SafeAreaView style={styles.container} forceInset={{ top: "always" }}>
</SafeAreaView>
);
});
so now, when i call the useLoadAsyncProfileDependencies function, i get this error.
The Problem is that i call useStores in helper.js
so when i pass the Stores from the Screen to the helper it is working.
export const loadAsyncProfileDependencies = async ({
ExamsStore,
UserStore,
CTAStore,
AnswersStore
}) => {
const userID = UserStore.userID;
if (userID) {
UserStore.initUser().then(user => {
UserStore.user = user;
});
CTAStore.getAllCTAS(userID).then(ctas => {
console.log("test", ctas);
CTAStore.ctas = ctas;
});
AnswersStore.getAllAnswers(userID).then(answers => {
AnswersStore.answers = answers.items;
ExamsStore.initExams(answers.items);
});
}
};
Is there a better way? instead passing the Stores.
So that i can use this function in functions?
As the error says, you can only use hooks inside the root of a functional component, and your useLoadAsyncProfileDependencies is technically a custom hook so you cant use it inside a class component.
https://reactjs.org/warnings/invalid-hook-call-warning.html
EDIT: Well after showing the code for app.js, as mentioned, hook calls can only be done top level from a function component or the root of a custom hook. You need to rewire your code to use custom hooks.
SEE THIS: https://reactjs.org/docs/hooks-rules.html
You should return the value for _handleAppStateChange so your useEffect's the value as a depdendency in your root component would work properly as intended which is should run only if value has changed. You also need to rewrite that as a custom hook so you can call hooks inside.
doTasksEveryTimeWhenAppWillOpenFromBackgorund and doTasksEveryTimeWhenAppGoesToBackgorund should also be written as a custom hook so you can call useLoadAsyncProfileDependencies inside.
write those hooks in a functional way so you are isolating specific tasks and chain hooks as you wish without violiating the rules of hooks. Something like this:
const useGetMyData = (params) => {
const [data, setData] = useState()
useEffect(() => {
(async () => {
const apiData = await myApiCall(params)
setData(apiData)
})()
}, [params])
return data
}
Then you can call that custom hook as you wish without violation like:
const useShouldGetData = (should, params) => {
if (should) {
return useGetMyData()
}
return null
}
const myApp = () => {
const myData = useShouldGetData(true, {id: 1})
return (
<div>
{JSON.stringify(myData)}
</div>
)
}

asynchronous context with useEffect in React

im trying to create an api request with the header value, that is received from a context component. However, as soon as the page component is loaded, it throws an Cannot read property '_id' of null exception. Is there a way to run the useEffect function, as soon as the context is loaded?
main component:
import React, { useState, useEffect, useContext } from "react";
import "./overview.scss";
/* COMPONENTS */;
import axios from 'axios';
import { GlobalContext } from '../../components/context/global';
const Overview = () => {
const [bookings, setBookings] = useState([]);
const [loaded, setLoaded] = useState(false);
const [user, setUser] = useContext(GlobalContext);
useEffect(() => {
axios
.get(`/api/v1/bookings/user/${user._id}`)
.then(res => setBookings(res.data))
.catch(err => console.log(err))
.finally(() => setLoaded(true));
}, [user]);
context component:
import React, {useState, useEffect, createContext} from 'react';
import jwt from 'jsonwebtoken';
/* GLOBAL VARIABLES (CLIENT) */
export const GlobalContext = createContext();
export const GlobalProvider = props => {
/* ENVIRONMENT API URL */
const [user, setUser] = useState([]);
useEffect(() => {
const getSession = async () => {
const user = await sessionStorage.getItem('authorization');
setUser(jwt.decode(user));
}
getSession();
}, [])
return (
<GlobalContext.Provider value={[user, setUser]}>
{props.children}
</GlobalContext.Provider>
);
};
The issue here is useEffect is running on mount, and you don't have a user yet. You just need to protect against this scenario
useEffect(() => {
if (!user) return;
// use user._id
},[user])
Naturally, when the Context fetches the user it should force a re-render of your component, and naturally useEffect should re-run as the dependency has changed.
put a condition before rendering you GlobalProvider, for example:
return (
{user.length&&<GlobalContext.Provider value={[user, setUser]}>
{props.children}
</GlobalContext.Provider>}
);
If user is not an array just use this
return (
{user&&<GlobalContext.Provider value={[user, setUser]}>
{props.children}
</GlobalContext.Provider>}
);

Resources