Custom hook getting undefined value then the correct object in react native? - javascript

Hello so am trying to make a custom hook where i get my user object from database and using it however my console log shows the the object is undefined shortly after object appears and this effect other function relying on it they only capture the first state and gives an error how can i fix that here is my code for the hook:-
import { useState, useEffect } from 'react'
import { storage, auth, database } from '../api/auth';
export default function useUser() {
const [user, setUser] = useState()
const userId = auth.currentUser.uid;
useEffect(() => {
getCurrentUser();
}, [])
const getCurrentUser = () => {
database.ref("users/" + userId).orderByChild('name').once("value").then(snapshot => {
setUser(snapshot.val())
})
}
return user
}

First of all you can assign default value to userObject.
const [user, setUser] = useState(null);
After place where you use useUser hook you can make a check is it null or not.
const user = useUser();
if (!user) {
return <Text>Loading...</Text>;
}
return(
<Text>{user.name}</Text>
);

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;

React - Can console log object, but not specific property of said object

I'm doing an exercise to learn React in which I have set up a page with a list of clickable pokemon names which are linking to the pokemons specific detail page. Below is the code of the details page
import { useState, useEffect } from "react";
import axios from "axios";
import { useParams } from "react-router-dom";
export default function DetailsPage() {
const pokeName = useParams();
console.log(pokeName);
const [pokeList, setPokeList] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
"https://pokeapi.co/api/v2/pokemon?limit=151"
);
console.log(response.data);
setPokeList(response.data.results);
};
fetchData();
}, []);
const specificPokemon = pokeList.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
console.log(specificPokemon);
console.log(specificPokemon.name);
return <div><p>{specificPokemon.name}</p></div>;
}
This code has an error I fail to understand
The console.log(specificPokemon) works fine, but the console.log(specificPokemon.name) gives me the following error
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
The correct code is the following, but I wonder why my method doesn't work
const [pokeList2, setPokeList2] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
`https://pokeapi.co/api/v2/pokemon/${pokeName.pokemon_name}`
);
console.log(response.data);
setPokeList(response.data);
};
fetchData();
}, []);
console.log(pokeList);
Thank you
When the code runs first the pokeList is an empty array and it cannot find the property name. You should create a second state and do something like this
const pokeName = useParams();
const [pokeList, setPokeList] = useState([]);
const [specificPokemon, setSpecificPokemon] = useState({});
useEffect(() => {
const fetchData = async () => {
const response = await axios.get(
"https://pokeapi.co/api/v2/pokemon?limit=151"
);
setPokeList(response.data.results);
const selectedPokemon = response.data.results.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
setSpecificPokemon(selectedPokemon)
};
fetchData();
}, [])
And don't forget to make the specificPokemon property optional like this specificPokemon?.name
When your component is mounted, pokeList is an empty array.
React will run the following block before the useEffect hook has finished running:
const specificPokemon = pokeList.find((pokemon) => {
return pokemon.name === pokeName.pokemon_name;
});
console.log(specificPokemon);
console.log(specificPokemon.name);
As long as your array is empty, specificPokemon will be undefined and calling specificPokemon.name will trigger your error.
Beware with console.log, its behavior is not always synchronous.
You might think specificPokemon is properly defined because console.log won't necessarily show undefined.
To verify this, use console.log(JSON.stringify(specificPokemon));.

reactjs: a context component function not updating its variable; usestate not working immediately?

I'm working on auth in my app, so I created a context to manage it:
import React, { useState } from "react";
const AuthContext = React.createContext({
token: "",
isLoggedIn: false,
login: (token) => { },
logout: () => { },
});
export const AuthContextProvider = (props) => {
const [token, setToken] = useState(null);
const [userIsLoggedIn, setUserLoggedIn] = useState(false)
const loginHandler = async (token) => {
setToken(token);
setUserLoggedIn(true)
console.log(userIsLoggedIn) //prints false
};
const logoutHandler = () => {
setToken(null);
setUserLoggedIn(false)
};
const contextValue = {
token: token,
isLoggedIn: userIsLoggedIn,
login: loginHandler,
logout: logoutHandler,
};
return (
<AuthContext.Provider value={contextValue}>
{props.children}
</AuthContext.Provider>
);
};
export default AuthContext;
The problem is that when I call context.login('some token') from an outside component and then console.log(context.isLoggedIn) I get false the first time, but if I call context.login('some token') once again I get true. Every succesive time after that I get true now. It might have to do with the fact the "console.log(userIsLoggedIn)" inside the loginHandler says "false" (the first time the context.login is called) even though the setUserLoggedIn is right above it. I think the userIsLoggedIn variable is changed after the context is updated in the component it's used in, but I don't know how to fix that.
I will appreciate any help on this
You cannot rely on the value of userIsLoggedIn to have the value you passed to setUserLoggedIn immediately.
If you want to keep track of the userIsLoggedIn variable, you might consider something like this:
React.useEffect(() => {
console.log(userIsLoggedIn)
}, [userIsLoggedIn])

How to use a custom-hook that returns a value, inside another custom Hook?

I am using React-native and in it, I have a custom Hook called useUser that gets the user's information from AWS Amplify using the Auth.getUserInfro method, and then gets part of the returned object and sets a state variable with it. I also have another Hook called useData hook that fetches some data based on the userId and sets it to a state variable.
useUser custom-Hook:
import React, { useState, useEffect } from "react";
import { Auth } from "aws-amplify";
const getUserInfo = async () => {
try {
const userInfo = await Auth.currentUserInfo();
const userId = userInfo?.attributes?.sub;
return userId;
} catch (e) {
console.log("Failed to get the AuthUserId", e);
}
};
const useUserId = () => {
const [id, setId] = useState("");
useEffect(() => {
getUserInfo().then((userId) => {
setId(userId);
});
}, []);
return id;
};
export default useUserId;
import useUserId from "./UseUserId";
// ...rest of the necessary imports
const fetchData = async (userId) = > { // code to fetch data from GraphQl}
const useData = () => {
const [data, setData] = useState();
useEffect(() => {
const userId = useUser();
fetchData(userId).then( // the rest of the code to set the state variable data.)
},[])
return data
}
When I try to do this I get an error telling me
*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:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
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.*
I think the problem is that I am calling the Hook useUser inside of the use effect, but using it inside the function will cause the problem described here, and I can't use it outside the body of the fetchData since the useData itself is a hook, and it can be only used inside a functional component's or Hook's body. So I don't know how to find a way around this problem.
Correct, React hooks can only be called from React function components and other React hooks. The useEffect hook's callback isn't a React hook, it's a callback. According to the Rules of Hooks, don't call hooks inside loops, conditions, or nested functions.
I suggest refactoring the useData hook to consume the userId as an argument, to be used in the dependency array of the useEffect.
const fetchData = async (userId) => {
// code to fetch data from GraphQl
};
const useData = (userId) => {
const [data, setData] = useState();
useEffect(() => {
fetchData(userId)
.then((....) => {
// the rest of the code to set the state variable data.
});
}, [userId]);
return data;
};
Usage in Function component:
const userId = useUser();
const data = useData(userId);
If this is something that is commonly paired, abstract into a single hook:
const useGetUserData = () => {
const userId = useUser();
const data = useData(userId);
return data;
};
...
const data = useGetUserData();
Though you should probably just implement as a single hook as follows:
const useGetUserData = () => {
const [data, setData] = useState();
useEffect(() => {
getUserInfo()
.then(fetchData) // shortened (userId) => fetchData(userId)
.then((....) => {
// the rest of the code to set the state variable data.
setData(....);
});
}, []);
return data;
};
You can't call hook inside useEffect, Hook should be always inside componet body not inside inner function/hook body.
import useUserId from "./UseUserId";
// ...rest of the necessary imports
const fetchData = async (userId) => {
// code to fetch data from GraphQl}
};
const useData = () => {
const [data, setData] = useState();
const userId = useUser();
useEffect(() => {
if (userId) {
fetchData(userId).then(setData);
}
}, [userId]);
return data;
};

`object' is undefined due to async function that fetched data in useEffect hook in reactjs

I am fetching an object from api using axios.get("url"). The object fetched successfully (in Animal state) but there is a component level state (imageState) which requires updation using setState with fetched data. Code:Component:
import React,{useEffect, useState} from 'react'
import axios from 'axios'
const AnimalDetail = ({match}) => {
const [Animal ,setAnimal ] = useState({})
const Id = parseInt(match.params.id)
const [imageState, setImageState] = useState ("");
useEffect(()=>{
const fetchAnimal = async () => {
const {data} = await axios.get(`/api/animals/${Id}`)
setAnimal(data)
}
fetchAnimal()
// setImageState(Animal.image[0]) // need to access first index of image object
},[])
useEffect(()=>{
setImageState(Object.values(Animal.image)[0]) // error cant convert undefined to object
}
return (
<>
<h2>imageState </h2> //undefined
<h2>{typeof(Animal.image)}</h2> //gives object
</>
)
}
export default AnimalDetail
Backend Api :
{"id":2,
"image":["/image.jpg","/image2.jpg"],
"price":60000,
"breed":"",
"isAvailable":true,
"weight":110,
}
How can i fetch the data and update the component level state periodically(after fetching)?
You can try following, maybe this can help you. Removed the second useEffect and updated the image state in the first useEffect.
And also I can see, you have declared const [imageState, setImageState] = useState (""); twice. You can remove the second one.
Also, make sure you handle the API error in useEffect otherwise this may break the application on API failure.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const AnimalDetail = ({ match }) => {
const [Animal, setAnimal] = useState({});
const Id = parseInt(match.params.id);
const [imageState, setImageState] = useState('');
useEffect(() => {
const fetchAnimal = async () => {
const { data } = await axios.get(`/api/animals/${Id}`);
setAnimal(data);
setImageState(data.image[0]);
};
if (Id) {
fetchAnimal();
}
}, [Id]);
return (
<>
<h2>imageState </h2> //undefined
<h2>{typeof Animal.image}</h2> //gives object
</>
);
};
export default AnimalDetail;
your code has some error in the second useEffect.
you can use this one :
useEffect(() => {
if (Animal) setImageState(Object.values(Animal.image)[0]); // error cant convert undefined to object
}, [Animal]);
this is because the Animal should have value first.
and you are defining imageState two times in your code! the first one is enough.

Categories

Resources