React and Apollo useMutation --- Cannot read property '0' of undefined - reactjs

Can't seem to figure out away to handle this when the page loads. I continually get the error 'TypeError: Cannot read property '0' of undefined' when I load the component. The useMutation is using the react-apollo library, but for some reason it is checking if causes[0].name exists before we even run the mutation. The cause_name is initially empty on page load, but then first becomes undefined when we first interact with DonateBox. However then when we set a cause value, it correctly sends the array. So I'm not sure why this error continues to persist? Even if it is undefined, it shouldn't be complaining right? Any help would be great, it's driving me crazy.
const DonatePage = () => {
const [causes, setCauses] = useState('')
const [category, setCategory] = useState('cause')
const [amount, setAmount] = useState('25')
const [frequency, setFrequency] = useState('once')
const [saveDonation] = useMutation(CREATE_DONATION, {
variables: {
date: moment().toDate(),
type: 'cause',
recurring: 'false',
amount: Number(amount),
cause_name: undefined || null ? undefined : causes[0]?.name
},
onCompleted() {
}
})
return (
<DonateBox
goToPayment={setCurrentPage}
setCauseProps={setCauses}
setAmount={setAmount}
setCategory={setCategory}
/>
)
}

Pass your variables to the mutation when you call the mutation function. And remove variables from your useMutation() options.
const [saveDonation] = useMutation(CREATE_DONATION, {
onCompleted() {
}
});
...
...
...
saveDonation({
variables: {
date: moment().toDate(),
type: 'cause',
recurring: 'false',
amount: Number(amount),
cause_name: undefined || null ? undefined : causes[0].name
}
})

Related

How to execute React custom hook only when data is available or Apollo client loading is false

I have custom React hook which adding some scripts and add a variable to window object:
const useMyHook = ({ id, type }: Props) => {
useScript('https:domain/main.js');
useScript('https://domain/main2.js');
useEffect(() => {
window.mydata = { id: `${id}`, type: `${type}` };
}, [id]);
};
I am using Apollo client and GraphQl for fetching data.
In my Page component, when I console.log(myData) the data returns undefined and then right after it returns the data (without refreshing). I am using Fragments.
From useQuery hook I can get the loading variable. How do I have to use loading and my custom hook so when loading === false -> use my custom hook.
I tried something like this:
const foo = useMyHook({ id: myData.id, type: myData.type });
Then below in the component in the return:
return (
{!loading && foo}
// Rest of the component jsx code
)
But still sometimes it returns first undefined?
How can I fix this issue?
# Update:
For now I added another prop loading: boolean and added this to the custom hook:
useEffect(() => {
if (!loading) {
window.mydata = { id: `${id}`, type: `${type}` };
}
}, [id]);
Is this correct approach. And why does my fragment query first returns undefined?
You can add another prop like enabled for handling this issue
sth like this:
useMyHook.tsx :
const useMyHook = ({ id, type,enabled }: Props) => {
useScript('https:domain/main.js');
useScript('https://domain/main2.js');
useEffect(() => {
if(enabled)
{
window.mydata = { id: `${id}`, type: `${type}` };
}
}, [enabled]);
};
other component:
const foo = useMyHook({ id: myData.id, type: myData.type,enabled:!loading && (data || error) });
return foo
and you need to use data and error that can be deconstructed from useQuery (like loading ) to just make sure if loading is false and data or error exists (that's because of first time that the component renders, loading is false but and data and error doesn't exists because request is not submitted yet, that's why you see undefined first )

Cannot read properties of undefined (reading 'first')

I am trying to add data to a dynamic form in antd.
I get the data from firebase and use useEffect to get it.
When I try to run my code I get the error TypeError: Cannot read properties of undefined (reading 'first')
This error happened while generating the page. Any console logs will be displayed in the terminal
Here is what I have
const availablelinks = {
links: [
alllinks
]
};
<Form.List name="links" initialValue={
availablelinks.links.map((item) => {
console.log(item[0])
const lol = item[0]['first']
const zol = 'zol'
return {
first: lol,
last: zol,
};
}, [])
}>
here is the rest of my code https://wtools.io/paste-code/bEEc .
I think that the error has something to do with the useEffect (because when I edit the code and next.js updates the code in the browser there is nothing happening but when I refresh I get the error)
For reference here is how the form looks :
Now i am using
const [theArray, setTheArray] = useState([]);
... (see full code)
<Form.List name="links" initialValue={
theArray.map((item) => {
console.log(item)
const lol = item.first
const zol = item.last
return {
first: lol,
last: zol,
};
}, [])
}>
and I get https://imgur.com/a/WZIzNMo (it kinda works)
it get the state when it is changes but the form does not update

How to return a value from Firebase to a react component? [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed last year.
I am trying to read a value from RealtimeDatabase on Firebase and render it in an element, but it keeps returning undefined. I have the following code:
const getStudentName = (studentId) => {
firebase.database().ref('students').child(studentId).on("value", (snapshot) => {
return snapshot.val().name;
})
}
const StudentName = (studentId) => ( <p>{getStudentName(studentId)}</p> )
I know it's nothing wrong with the database itself or the value I'm finding, because if I do:
const getStudentName = (studentId) => {
firebase.database().ref('students').child(studentId).on("value", (snapshot) => {
console.log(snapshot.val().name);
return "Test";
})
}
I still see a correct name from my database outputted to console as expected, yet "Test" is not returned to the element. However, if I do it like this:
const getStudentName = (studentId) => {
firebase.database().ref('students').child(studentId).on("value", (snapshot) => {
console.log(snapshot.val().name);
})
return "Test";
}
then "Test" is returned to the element and displayed. I'm very confused, as I don't understand how my console.log() can be reached inside the function but a 'return' statement right after it will not return.
New to React and Firebase, please help! Thank you.
EDIT: I'm sure it's self-explanatory, but you can assume a simple database in the form:
{ "students": [
"0": { "name": "David" },
"1": { "name": "Sally" } ]}
If 'studentId' is 0 then 'console.log(snapshot.val().name)' successfully outputs 'David', but 'David' will not return to the element.
You can't return something from an asynchronous call like that. If you check in the debugger or add some logging, you'll see that your outer return "Test" runs before the console.log(snapshot.val().name) is ever called.
Instead in React you'll want to use a useState hook (or setState method) to tell React about the new value, so that it can then rerender the UI.
I recommend reading the React documentation on the using the state hook, and the documentation on setState.
I'm not sure where you are consuming getStudentName, but your current code makes attaches a real-time listener to that database location. Each time the data at that location updates, your callback function gets invoked. Because of that, returning a value from such a function doesn't make much sense.
If you instead meant to fetch the name from the database just once, you can use the once() method, which returns a Promise containing the value you are looking for.
As another small optimization, if you only need the student's name, consider fetching /students/{studentId}/name instead.
const getStudentName = (studentId) => {
return firebase.database()
.ref("students")
.child(studentId)
.child("name")
.once("value")
.then(nameSnapshot => nameSnapshot.val());
}
With the above code, getStudentName(studentId) now returns a Promise<string | null>, where null would be returned when that student doesn't exist.
getStudentName(studentId)
.then(studentName => { /* ... do something ... */ })
.catch(err => { /* ... handle errors ... */ })
If instead you were filling a <Student> component, continuing to use the on snapshot listener may be the better choice:
const Student = (props) => {
const [studentInfo, setStudentInfo] = useState({ status: "loading", data: null, error: null });
useEffect(() => {
// build reference
const studentDataRef = firebase.database()
.ref("students")
.child(props.studentId)
.child("name");
// attach listener
const listener = studentDataRef.on(
'value',
(snapshot) => {
setStudentInfo({
status: "ready",
data: snapshot.val(),
error: null
});
},
(error) => {
setStudentInfo({
status: "error",
data: null,
error
});
}
);
// detach listener in unsubscribe callback
return () => studentDataRef.off(listener);
}, [props.studentId]); // <- run above code whenever props.studentId changes
// handle the different states while the data is loading
switch (studentInfo.status) {
case "loading":
return null; // hides component, could also show a placeholder/spinner
case "error":
return (
<div class="error">
Failed to retrieve data: {studentInfo.error.message}
</div>
);
}
// render data using studentInfo.data
return (
<div id={"student-" + props.studentId}>
<img src={studentInfo.data.image} />
<span>{studentInfo.data.name}</span>
</div>
);
}
Because of how often you might end up using that above useState/useEffect combo, you could rewrite it into your own useDatabaseData hook.

ReactJS context-api won't render after I get data

This is a next.js site, since both my Navbar component and my cart page should have access to my cart's content I created a context for them. If I try to render the page, I get:
Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading 'key')
obs: The cartContent array exists and has length 1, I can get it by delaying when the data's rendered by using setTimeout, but, can't get it to render right after it's fetched.
I need to make it render after the data from firebase is returned, but always met with the mentioned error.
This is my _app.tsx file
function MyApp({ Component, pageProps }) {
// set user for context
const userContext = startContext();
return (
<UserContext.Provider value = { userContext }>
<Navbar />
<Component {...pageProps} />
<Toaster />
</UserContext.Provider>
);
}
export default MyApp
This file has the startContext function that returns the context so it can be used.
export const startContext = () => {
const [user] = useAuthState(auth);
const [cart, setCart] = useState(null);
const [cartContent, setCartContent] = useState(null);
useEffect(() => {
if (!user) {
setCart(null);
setCartContent(null);
}
else {
getCart(user, setCart, setCartContent);
}
}, [user]);
return { user, cart, setCart, cartContent, setCartContent };
}
This file contains the getCart function.
export const getCart = async (user, setCart, setCartContent) => {
if (user) {
try {
let new_cart = await (await getDoc(doc(firestore, 'carts', user.uid))).data();
if (new_cart) {
let new_cartContent = []
await Object.keys(new_cart).map(async (key) => {
new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
});
console.log(new_cartContent);
setCartContent(new_cartContent);
console.log(new_cartContent);
setCart(new_cart);
}
else {
setCart(null);
setCartContent(null);
}
} catch (err) {
console.error(err);
}
}
}
This is the cart.tsx webpage. When I load it I get the mentioned error.
export default () => {
const { user, cart, cartContent } = useContext(UserContext);
return (
<AuthCheck>
<div className="grid grid-cols-1 gap-4">
{cartContent && cartContent[0].key}
</div>
</AuthCheck>
)
}
I've tried to render the cart's content[0].key in many different ways, but couldn't do it. Always get error as if it were undefined. Doing a setTimeout hack works, but, I really wanted to solve this in a decent manner so it's at least error proof in the sense of not depending on firebase's response time/internet latency.
Edit:
Since it works with setTimeout, it feels like a race condition where if setCartContent is used, it triggers the rerender but setCartContent can't finish before stuff is rendered so it will consider the state cartContent as undefined and won't trigger again later.
Try changing
{cartContent && cartContent[0].key}
to
{cartContent?.length > 0 && cartContent[0].key}
Edit:: The actual problem is in getCart function in line
let new_cart = await (await getDoc(doc(firestore, 'carts', user.uid))).data();
This is either set to an empty array or an empty object. So try changing your if (new_cart) condition to
if (Object.keys(new_cart).length > 0) {
Now you wont get the undefined error
Since there seemed to be a race condition, I figured the setCartContent was executing before its content was fetched. So I changed in the getCart function the map loop with an async function for a for loop
await Object.keys(new_cart).map(async (key) => {
new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
});
to
for (const key of Object.keys(new_cart)) {
new_cartContent.push({...(await getDoc(doc(firestore, 'products-cart', key))).data(), key: key});
}
I can't make a map function with await in it without making it asynchronous so I the for loop made it work. Hope someone finds some alternatives to solving this, I could only come up with a for loop so the code is synchronous.

Way to avoid non-null assertion while using useState in async Data?

Trying to use strict typescript with Next.js and React-query
There is problem that useQuery's return data is Post | undefined.
So I should make data given with useQuery not null with ! operator while allocating to useState
ESlint does not like non-null type assertion.
I know I could turn it off... But I want to do strict type check so I don't want to avoid this.
One way that I found was to use if statement to do null check
but React Hooks is not able to wrap it with if statement...
Is there any brilliant way to do it?
My code is like this
const Edit = () => {
const router = useRouter();
const { id } = router.query;
const { data } = useQuery<Post>('postEdit', () => getPost(id as string));
const [post, setPost] = useState<TitleAndDescription>({
title: data!.title,
content: data!.content,
});
const editMutation = usePostEditMutation();
return (
<MainLayout>
<Wrapper>
{data && <Editor post={post} setPost={setPost} />}
</Wrapper>
</MainLayout>
);
};
export interface TitleAndDescription {
title: string;
content: Descendant[];
}
You can do
const [post, setPost] = useState<TitleAndDescription>({
title: data?.title ?? '',
content: data?.content ?? []
});
The non-null assertion is for cases when you want to exclude null or undefined values from the type. For most cases you don't need it, you can cast to the type you want:
(data as Post).title
or provide a fallback value:
data?.title || ''
While you can do:
const [post, setPost] = useState<TitleAndDescription>({
title: data?.title ?? '',
content: data?.content ?? []
});
as the current accepted answer suggests, it won't solve your problem as you will always have undefined in your local state, as it won't update automatically when new data comes in from useQuery.
data will be undefined on the first render because react-query needs to fetch it first. With useState, you are only passing in the initial value of the first render - which is undefined.
There is no need to copy data from react-query to local state. This just duplicates the single source of truth. You can just use the data returned from useQuery as it will always reflect the server value:
const { data } = useQuery<Post>('postEdit', () => getPost(id as string));
const post = data ?? { title: '', content: [] }

Resources