Uncaught TypeError: Cannot read properties of undefined (reading 'question'). This is after using useState() to set the data from a GET request - javascript

I am making a simple GET request and want to make that data more accessible using useState() but it seems as though this error is caused by accessing an property that does not exist due to useState not updating it?
Even though I have made GET requests very similar to this, it is the first time I am using useLocation(). I'm not sure if that has anything to do with the problem or it has something to do with useState().
Any response is much appreciated
const getQuiz = async () => {
try{
// These values were passed by the difficulty Component
const categoryName = location.state?.name
const difficulty = location.state?.difficulty
// This makes a get request to get data for the quiz
let response = await axios.get(`https://the-trivia-api.com/api/questions?categories=${categoryName}&limit=10&difficulty=${difficulty}`)
let arrayDataResponse = await response.data
// This sets the data to question array so that it is accessible outside of this function
setQuestionArray(arrayDataResponse)
// this outputs an empty array
console.log(questionArray)
} catch(err){
console.log(err)
}
}
// This fetches the data on mount
useEffect(() => { getQuiz() }, [])
// This will set the data for the elements once the state of the question array has been set from the get request
useEffect(() => {
// This sets the content for the question element
setQuestion(questionArray[0].question)
// <h4>{question}</h4>
// Uncaught TypeError: Cannot read properties of undefined (reading 'question')
}, [questionArray])

I'm guessing that your state is defined something like this...
const [questionArray, setQuestionArray] = useState([]);
const [question, setQuestion] = useState(/* some initial value */);
This means that when your component is initialised and mounted, questionArray is an empty array.
Effect hooks not only execute when their dependencies change but also when they are initialised. That means when this hook first runs...
useEffect(() => {
setQuestion(questionArray[0].question);
}, [questionArray]);
It's trying to access .question on undefined, hence your error.
I would skip the question state and the above hook entirely. If you want something to represent the optional first question, you can use a memo hook instead
const firstQuestion = useMemo(() => questionArray[0]?.question, [questionArray]);
or simply use questionArray[0]?.question directly without any hooks.
This will either return the first question property or undefined which you can detect using conditional rendering
{firstQuestion && (
<p>{firstQuestion}</p>
)}
{/* or */}
{questionArray.length > 0 && (
<p>{questionArray[0].question}</p>
)}

const getQuiz = async () => {
try{
// These values were passed by the difficulty Component
const categoryName = location.state?.name
const difficulty = location.state?.difficulty
// This makes a get request to get data for the quiz
let response = await axios.get(`https://the-trivia-api.com/api/questions?categories=${categoryName}&limit=10&difficulty=${difficulty}`)
let arrayDataResponse = await response.data
// This sets the data to question array so that it is accessible outside of this function
setQuestionArray(arrayDataResponse)
//Solution
// set question from here
setQuestion(arrayDataResponse[0].question)
// this outputs an empty array
console.log(questionArray)
} catch(err){
console.log(err)
}
}
// then you don't need to run useEffect for this , Your state will be fiilled with api response
// not required code below
useEffect(() => {
// This sets the content for the question element
setQuestion(questionArray[0].question)
// <h4>{question}</h4>
// Uncaught TypeError: Cannot read properties of undefined (reading 'question')
}, [questionArray])

Related

Why changes made in database not updates in realtime?

I am trying to learn firestore realtime functionality.
Here is my code where I fetch the data:
useEffect(() => {
let temp = [];
db.collection("users")
.doc(userId)
.onSnapshot((docs) => {
for (let t in docs.data().contacts) {
temp.push(docs.data().contacts[t]);
}
setContactArr(temp);
});
}, []);
Here is my database structure:
When I change the data in the database I am unable to see the change in realtime. I have to refresh the window to see the change.
Please guide me on what I am doing wrong.
Few issues with your useEffect hook:
You declared the temp array in the way that the array reference is persistent, setting data with setter function from useState requires the reference to be new in order to detect changes. So your temp array is updated (in a wrong way btw, you need to cleanup it due to now it will have duplicates) but React is not detectign changes due to the reference to array is not changed.
You are missing userId in the dependency array of useEffect. If userId is changed - you will continue getting the values for old userId.
onSnapshot returns the unsubscribe method, you have to call it on component unMount (or on deps array change) in order to stop this onSnapshot, or it will continue to work and it will be a leak.
useEffect(() => {
// no need to continue if userId is undefined or null
// (or '0' but i guess it is a string in your case)
if (!userId) return;
const unsub = db
.collection("users")
.doc(userId)
.onSnapshot((docs) => {
const newItems = Object.entries(
docs.data().contacts
).map(([key, values]) => ({ id: key, ...values }));
setContactArr(newItems);
});
// cleanup function
return () => {
unsub(); // unsubscribe
setContactArr([]); // clear contacts data (in case userId changed)
};
}, [userId]); // added userId

Console log inside React 'useEffect' function doesn't work after page refresh [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 1 year ago.
The 'useEffect' function is being used to retrieve data and then store it in the 'ratings' array. I'm currently trying to loop through the 'ratings' array elements in order to console log their contents. This works when the component is first mounted but stops working once the page is refreshed. I found that I can get it to work by placing the 'forEach' outside the useEffect function but was wondering why it doesn't work inside the function and if there's another solution.
const Rating = ({id}) => {
let url=`https://api.jikan.moe/v3/anime/${id}/stats`;
const [ratings, setRatings] = useState([]);
useEffect(() =>
{
(async function()
{
try{
const res = await axios.get(url);
setRatings(Object.values(res.data.scores));
if (ratings) ratings.forEach((rating)=>{console.log(rating.votes)}); //Doesn't work after refresh
}
catch(err)
{
console.log(err);
}
})();
}, [url])
...
}
export default Rating;
When you do setRatings(Object.values(res.data.scores));, the ratings state is not updated imediatelly, so if (ratings) is still the empty array when the app runs if (ratings) ....
Do something like this instead:
const scores = Object.values(res.data.scores);
setRatings(scores);
if (scores) scores.forEach((rating)=>{console.log(rating.votes)});
or use another useEffect for the ratings so it runs when it gets updated:
useEffect(() => {
if (ratings) ratings.forEach((rating)=>{console.log(rating.votes)});
}, [ratings]);

How to render something that is async in React?

I'm making a react app that works with a API that provides data to my App. In my data base I have data about pins on a map. I want to show the info of those pins on my react app, I want them to render. I get that information with axios and this url: http://warm-hamlet-63390.herokuapp.com/pin/list
I want to retrieve the info from that url, with axios.get(url), stringify the JSON data and then parse it to an array of pins.
The Problem:
My page will be rendered before I get the data back from the server, because axios is async, so I will not be able to show anything. UseEffect and useState won't work because I need something in the first place (I think).
What i've tried:
I tried to use useEffect and useState, but as I said, I think I need something in the first place to change it after. I also tried to use await, but await won't stop the whole React App until it has a response, although it would be nice if there is something that stops the app and waits until I have the array with the info so I can show it on the App then. I tried everything with async. I'm fairly new to React so there might be something basic i'm mssing (?). I've been on this for days, I can't get this to work by any means.. Any help, youtube videos, documentation, examples, is help. Anything. How the hell do I render something that needs to wait for the server respond?
My code:
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
async function pin(){
const result = []
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
const res = await axios.get(url)
console.log(res.data.data);
if(res.data){
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
console.log(result);
}
return result;
}
class App extends React.Component{
render(){
return(
<div>
<Pin/>
<Mapa/>
</div>
)
}
}
export default App
I don't fully understand what you are trying to output but how you would usually handle this is with both the useState hook and the useEffect hook see example below.
//function that stores the data in the result array,
//but result array will only be available after the
//server response, and after the page is rendered
const pin = () => {
const [result, setResults] = useState([]);
var url = "http://warm-hamlet-63390.herokuapp.com/pin/list"
useEffect(() => {
//Attempt to retreive data
try {
const res = transformData();
if (res) {
// Add any data transformation
setResults(transformData(res))
}
else {
throw (error)
}
}
catch (error) {
//Handle error
}
}, [])
// Handle data transformation
const transformData = async () => {
const res = await axios.get(url)
const txt = JSON.stringify(res.data.data)
const result = JSON.parse(txt)
return result
}
if (!result) {
// Return something until the data is loaded (usually a loader)
return null
}
// Return whatever you would like to return after response succeeded
return <></>;
}
This is all assuming that Pin is a component like you have shown in your code, alternatively, the call can be moved up to the parent component and you can add an inline check like below to render the pin and pass some data to it.
{result && <Pin property={someData} />}
Just a bit of background the useEffect hook has an empty dependency array shown at the end "[]" this means it will only run once, then once the data has updated the state this will cause a rerender and the change should be visible in your component
Rest assured, useEffect() will work. You need to use a condition to conditionally render the content when it comes back from the server.
In the example below if results has a length < 1 the message Loading ... will be rendered in the containing <div>, once you're results are received the state will be updated (triggering a re-render) and the condition in the template will be evaluated again. This time though results will have a length > 1 so results will be rendered instead of Loading ...
I’m operating under the assumption that you’re function pin() is returning the results array.
const app = (props) => {
const [results, setResult] = useState([]);
React.useEffect(() => {
const getPin = async () => {
if (!results) {
const results = await pin();
setResult([…results])
}
}
getPin();
},[results]);
return (
<div>
{result.length ? result : 'Loading ... '}
</div>
)
}

Undefined is not an object while trying to query nested objects. Using axios and React

The JSON response is as shown in the image 1.
I was able to assign the entire response using axios (which already does the JSON.parse) to the state (named profile).
while profile.bio and profile.image are working;
profile.user.username, etc are not working and throwing an error - Undefined is not an object
const [profile, setProfile] = useState({});
const phone_no = phone.phone;
const fetchProfile = useEffect(() => {
var res = {};
axios
.get('<API URL>' + phone_no)
.then((response) => (res = response.data))
.then(() => {
setProfile(res);
})
.then(() => console.log(profile))
.catch((e) => console.log(e)); });
const user_stream = {
name: first.first_name,
image: profile.image,
id: profile.user.id,
};
Update - Solution: Using async-await with axios, it's fixed.
profile or profile.user may still be undefined when trying to access it, so profile.bio is just undefined so it doesn't cause an error, but profile.user.username tries to access a property of an undefined object.
Try adding profile?.user?.username
Or profile && profile.user && profile.user.username
This will ensure that it only tries to render the username if profile is already defined

Why is my custom hook called so many times?

I'm trying to implement a custom hook to provide the app with a guest shopping cart. My hook wraps around the useMutation hook from Apollo and it saves the shopping cart id in a cookie while also providing a function to "reset" the cart (basically, to remove the cookie when the order is placed).
Code time! (some code omitted for brevity):
export const useGuestCart = () => {
let cartId;
const [createCart, { data, error, loading }] = useMutation(MUTATION_CREATE_CART);
console.log(`Hook!`);
if (!cartId || cartId.length === 0) {
createCart();
}
if (loading) {
console.log(`Still loading`);
}
if (data) {
console.log(`Got cart id ${data.createEmptyCart}`);
cartId = data.createEmptyCart;
}
const resetGuestCart = useCallback(() => {
// function body here
});
return [cartId, resetGuestCart];
};
In my component I just get the id of the cart using let [cartId, resetCart] = useGuestCart(); .
When I run my unit test (using the Apollo to provide a mock mutation) I see the hooked invoked several times, with an output that looks something like this:
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:59
Still loading
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:62
Got cart id guest123
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
I'm only getting started with hooks, so I'm still having trouble grasping the way they work. Why so many invocations of the hook?
Thank you for your replies!
Think of hooks as having that same code directly in the component. This means that every time the component renders the hook will run.
For example you define:
let cartId;
// ...
if (!cartId || cartId.length === 0) {
createCart();
}
The content inside the statement will run on every render as cartId is created every time and it doesn't have any value assigned at that point. Instead of using if statements use useEffect:
export const useGuestCart = () => {
const [cartId, setCartId] = useState(0);
const [createCart, { data, error, loading }] = useMutation(
MUTATION_CREATE_CART
);
const resetGuestCart = () => {
// function body here
};
useEffect(() => {
if(!cartId || cartId.length === 0){
createCart();
}
}, [cartId]);
useEffect(() => {
// Here we need to consider the first render.
if (loading) {
console.log(`Started loading`);
} else {
console.log(`Finished loading`);
}
}, [loading]);
useEffect(() => {
// Here we need to consider the first render.
console.log(`Got cart id ${data.createEmptyCart}`);
setCartId(data.createEmptyCart);
}, [data]);
return [cartId, resetGuestCart];
};
Also notice that there is no actual benefit from using useCallback if the component which is receiving the function is not memoized.

Categories

Resources