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, []);
}
Related
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
I encounter the following error:
Error: FIRESTORE (9.6.2) INTERNAL ASSERTION FAILED: Unexpected state
while trying to test my React component:
export default function ImageList() {
const [imagesUrl, setImagesUrl] = useState([]);
useEffect( () => {
const unsubscribe = getImages(setImagesUrl);
return () => unsubscribe();
}, []);
return (
// JSX
)
}
getImages is a function that subscribes to Firebase Firestore with onSnapShot function:
import { collection, onSnapshot from "firebase/firestore";
import { database } from "./setup";
const getImages = ( handleResponse ) => {
const unsubscribe = onSnapshot( collection(database, "/images"), snapshot => {
const _imagesUrl = [];
snapshot.forEach((doc) => {
const data = doc.data();
_imagesUrl.push(data.imageUrl);
});
handleResponse(_imagesUrl);
} )
return unsubscribe;
}
export default getImages;
Below is my test component:
import { render, screen } from '#testing-library/react';
import ImageList from "./ImageList";
test("list of images exist", async () => {
render( <ImageList /> );
const imgElement = await screen.findByRole("img");
expect(imgElement).toBeInTheDocument();
})
I have followed this Stack Overflow answer to isolate my React component with Firebase logic so that I can test each of them separately with React Testing Library and Jest, but it does not seem to work in my case.
Do you know I can resolve this issue?
New to react and hooks, I am trying to do a login module using hooks. However when I am not able to update the state of my Auth state. Read elsewhere that useState do not update immediately and needs to be coupled with useEffect() to have it updated. However I am using useState in a custom hook and not sure how to have updated either through useEffect or other means.
Am I doing some kind of anti-pattern here? Anyone able to help?
const useAuth = () => {
const [auth, setAuth] = useState( {} );
useEffect(()=>{
console.log(auth)
},[])
return {auth, setAuth}
}
export const useHandleLogin = (props) => {
const {auth, setAuth} = useAuth()
const history = useHistory();
useEffect(()=>{
console.log(auth)
},[])
const login = () => {
console.log('login action called---- ');
/* check if user is login */
if(localStorage.getItem('user')){
console.log('got user' );
} else {
console.log('no user, calling backend to authenticate... ' );
// change to login api
axios.get(`http://localhost:3001/projects`)
.then(res => {
console.log('call api' /* + JSON.stringify(res) */);
})
localStorage.setItem('user', JSON.stringify({username:'abc',role:'123'}))
console.log('login done' + JSON.stringify(auth));
console.log('login done2' + auth.authenticated);
}
setAuth({
authenticated: true,
displayName: 'My Name',
email: 'xxx#abc.com',
role: 'admin'
})
console.log("sending to success page" + auth + JSON.stringify(auth)) // Error is here. output is : sending to success page[object Object]{}
import React, {useEffect} from "react";
import { useHandleLogin } from "./LoginUtil"
const TestLoginPage = () => {
const { auth, login } = useHandleLogin();
const Clogin = () => {
console.log('auth: ' + auth)
login();
};
return (
<React.Fragment>
<div>login</div>
<button onClick={Clogin} > Login </button>
</React.Fragment>
);
}
export default TestLoginPage;
Seems you are missing to add some dependency in useEffect() dependency array. Also, in order to generate new instance of a callback like login() you should wrap it in useCallback() and add necessary dependencies in the array. From the look of it,
useEffect() in missing auth in dependency array
login() should be wrapped in useCallback() and must have auth in dependency
array
Clogin() must be wrapped in useCallback() and must have auth and login
in dependency array
You can use add eslint-plugin-react-hooks which can help you in prompting warnings if you miss dependency.
After couple of days of trying and reading up, below is what I believe to be the correct implementation.
import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import { Redirect } from "react-router-dom";
import axios from 'axios';
export const useAuth = (props) => {
/* const init = localStorage.getItem('user1')? true : false */
const init = {authen : false}
const [auth, setAuth] = useState(init);
const history = useHistory();
console.log("auth initial " + auth)
const checkAuthStatus = () => {
return !!auth.authen
}
const login = () => {
console.log('login action called---- ');
let success = true
if(success){
setAuth({authen : true})
}else{
setAuth ({authen : false})
}
console.log('push history==========' )
/* history.push('/testLoginOK');
history.go(); */
}
const logout = () => {
console.log('logout action called---- ');
setAuth ({authen : false})
history.push('/testLogin');
history.go();
}
useEffect(() => {
console.log("useEffect auth "+auth.authen)
if(!!auth.authen){
/* history.push('/testLoginOK');
history.go(); */
}
})
return {auth, checkAuthStatus, login, logout}
}
/* export default useAuth */
I want to have a way to get and fetch the current user using React Context (to not pass props to all my components)
I tried using React Context but didn't understand how I would achieve something like const { currentUser, fetchCurrentUser } = useCurrentUser() from the docs.
here is what i did for my project:
// src/CurrentUserContext.js
import React from "react"
export const CurrentUserContext = React.createContext()
export const CurrentUserProvider = ({ children }) => {
const [currentUser, setCurrentUser] = React.useState(null)
const fetchCurrentUser = async () => {
let response = await fetch("/api/users/current")
response = await response.json()
setCurrentUser(response)
}
return (
<CurrentUserContext.Provider value={{ currentUser, fetchCurrentUser }}>
{children}
</CurrentUserContext.Provider>
)
}
export const useCurrentUser = () => React.useContext(CurrentUserContext)
and then use it like this:
setting up the provider:
// ...
import { CurrentUserProvider } from "./CurrentUserContext"
// ...
const App = () => (
<CurrentUserProvider>
...
</CurrentUserProvider>
)
export default App
and using the context in components:
...
import { useCurrentUser } from "./CurrentUserContext"
const Header = () => {
const { currentUser, fetchCurrentUser } = useCurrentUser()
React.useEffect(() => fetchCurrentUser(), [])
const logout = async (e) => {
e.preventDefault()
let response = await fetchWithCsrf("/api/session", { method: "DELETE" })
fetchCurrentUser()
}
// ...
}
...
the full source code is available on github: https://github.com/dorianmarie/emojeet
and the project can be tried live at: http://emojeet.com/
If useContext returns undefined, then you might have forgotten the Provider or need to move your provider up the stack.
U dont explained what u want to do with the data but...After u exec the function fetch in use effect.
Now u have the object user in the state currentUser.
Try to console log after the use effect the currentUser and see what dat inside it.
After u can use it with currentUser."whatever prop inside"..
I took a two day React training class last week. The instructor tried to jam a weeks worth of info into two days. We slapped together a crude blackjack game in the "class" (or is that Class<T>: => () await (...p) ha ha little Typescript joke there.) The class did not get around to covering how to access a REST API. So here I am stuck with a bunch of confusing unreadable code that I am not even sure is React or Typescript. Any whooo this is my App.tsx file:
import React, {useState} from 'react';
import './App.css';
import {fetchUser, User, UserCtx} from "./User";
import {useAsync} from "./ss-react-utils";
const AppLoaded = ({user}: {user: User}) => {
return <div className="App">
{user.userName}
</div>;
};
const AppLoading = () => {
return <div>Loading ...</div>;
};
const App: React.FC = () => { // I guess the FC stands for F*king Confusing
const [user, setUser] = useState<User | null>(null);
useAsync({
op: fetchUser,
onSuccess: setUser,
deps: []
});
if (user !== null) {
return <AppLoaded user={user} />;
} else {
return <AppLoading/>;
}
}
export default App;
Here's the ss-react-utils.tsx file:
import React, {CSSProperties, DependencyList, useEffect} from "react";
export type Op<T> = () => Promise<T>;
export type OnSuccess<T> = (data: T) => void;
export function useAsync<T>({op, onSuccess, deps}: { op: Op<T>, onSuccess: OnSuccess<T>, deps?: DependencyList }) {
useEffect(() => {
const doOp = async () => {
const data: T = await op();
onSuccess(data);
};
doOp();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);
}
... some other unrelated ( I think ) code ...
when I run that code I get this ...
I would like to know how to handle the error and display the login page to the user if the REST API returns a 403 forbidden error.
And Yes I know the REST API is returning a 403 error. So please don't answer the question with you are getting a 403 error. :)
Also I have seen a lot of answers that say use componentDidMount but the instructor said that was the old way of doing React and he was going to teach about hooks instead. Well would like to try what the instructor was getting at before I completely give up on the "New" way to code React. And probably React all together. So please help :)
UPDATE: I am using Django Rest Framework and it is behaving as expected.
const App: React.FC<{id}> = ({id}) => {
const op = useCallback(()=> fetchUser(id), [id]);
const { data: user, loading, error } = useFetch(op);
useEffect(()=> {
if (!error){return}
alert(error); // if wanna alert error;
}, [error]);
return (
<>
{loading && <AppLoading />}
{user && <AppLoaded user={user}/>}
{error && <pre>{error.message | error}</pre>}
</>
)
}
function useFetch<T>(op: ()=> Promise<T>){
const [loading, setLoading] = useState(false);
const [data, setData] = useState<T | null | undefined>();
const [error, setError] = useState<string | null>(null);
const id = useRef(0);
useEffect(()=> ()=> {
// on unmount
id.current = 0;
}, []);
useEffect(()=> {
const currentID = ++id.current;
setLoading(true);
op().then((data)=> {
// ignore when query changed
if (id.current !== currentID) { return; }
setData(data);
setLoading(false);
setError(null);
}, (err)=> {
if (id.current !== currentID) { return; }
setError(err.message || err);
setLoading(false);
})
}, op);
return {data, loading, error}
}
In my last project, I have a problem with async download data from api. Then I must use something like this:
componentDidMount() {
fetch("API")
.then(res => res.json())
.then(json => {
...
this.setState({ data });
});
}
But this code is for the class component. Can you test this same code with only fetch data?
We will exclude in this way whether it is the fault of the data download itself.
You can handle error in this way. Here is the example:-
componentDidMount() {
fetch(URL)
.then(results => results.json())
.then(data => this.setState({ results: data }))
.catch(error=>{ //here you can handel errors
if(error.response){
alert("Error occurred");
}
});
}