React state is undefined when fetching data from sanity cms - javascript

I followed a tutorial recently about integrating a cms into your website. The tutorial used sanity cms which made the process very intuitive. Once I was done with the tutorial I was ready to use it in my own projects.
however when I try to fetch data with the useEffect hook I get an error: Cannot read properties of undefined. I know this is because fetching data is done async. But the thing I can't wrap my head around is I did it the exact same way as the tutorial. He didn't use any state for loading or isFetched. So my question is what did I do different than the tutorial and how should I solve it?
I don't really want to use a loading state because that doesn't really look that good...
This is the JSON object I receive from the api:
[{…}]
0:
buttonlabel: "Start learning"
description: "Ranging from beginner to pro level tricks. Wanna know the best way to learn a trick? You can search for it down below and find a tutorial from one of our trainers as well as a detailed explanation. Still stuck? Come ask us at a Westsite training moment."
intro: "Welcome to the Westsite trick progression guide. Here you can find a collection of all the wakeboarding tricks you can think of. "
_createdAt: "2022-05-24T16:26:13Z"
_id: "a4f8cf02-4b86-44d5-a63d-c95a3a7d3293"
_rev: "QYLgvM20Eo53w3noOOj0MB"
_type: "hero"
_updatedAt: "2022-05-24T17:29:10Z"
This is the tutorial component:
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { urlFor, client } from "../../client";
import { AppWrap, MotionWrap } from "../../wrapper";
import "./About.scss";
const About = () => {
const [abouts, setAbouts] = useState([]);
useEffect(() => {
const query = '*[_type == "abouts"]';
client.fetch(query).then((data) => setAbouts(data));
}, []);
return (
<div>
<h2 className="head-text">
I know that
<span> Good Design </span>
<br />
means
<span> Good Business</span>
</h2>
<div className="app__profiles">
{abouts.map((about, index) => {
return (
<motion.div
whileInView={{ opacity: 1 }}
whileHover={{ scale: 1.1 }}
transition={{ duration: 0.5, type: "tween" }}
className="app__profile-item"
key={about.title + index}
>
<img src={urlFor(about.imgUrl)} alt={about.title} />
<h2 className="bold-text" style={{ marginTop: 20 }}>
{about.title}
</h2>
<p className="p-text" style={{ marginTop: 10 }}>
{about.description}
</p>
</motion.div>
);
})}
</div>
</div>
);
};
export default AppWrap(
MotionWrap(About, "app__about"),
"about",
"app__whitebg"
);
And this is mine:
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { BiRightArrowAlt } from "react-icons/bi";
import { client } from "../../client";
import "./Hero.scss";
const Hero = () => {
const [heroContent, setHeroContent] = useState([]);
useEffect(() => {
const query = '*[_type == "hero"]';
client.fetch(query).then((data) => setHeroContent(data));
}, []);
const content = heroContent[0];
return (
<div className="app__hero">
<motion.div
className="app__hero-content-container"
whileInView={{ opacity: [0, 1], x: [500, 0] }}
transition={{ duration: 1, ease: "easeOut" }}
>
<div className="app__hero-content">
<h2 className="heading-text">
Learn
<span className="highlighted"> wakeboarding </span>
the right way
</h2>
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
</div>
</motion.div>
</div>
);
};
export default Hero;

This line will cause issues before the data is applied to state asynchronously
const content = heroContent[0];
On the initial render, heroContent is an empty array, so content will be undefined until your data is loaded. A couple options -
1 - render some sort of loading state until heroContent has been populated -
if (!heroContent.length) return <LoadingSpinner />
2 - wrap the portion that is trying to use content with a guard clause
{content && (
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
)}

The issue comes when you try to access properties from content when it's undefined. If you don't want to show any loading indicator, I would go with showing some fallback for when content is not defined. e.g.
Instead of:
<p className="p-text">{content.intro}</p>
You could go with:
<p className="p-text">{content?.intro ?? '-'}</p>
or something like that.

Problem is that you breakdown your state on different level that create problem with state changes. So, you have to do this
Either you call state as map function or save your state with specfic index 0.
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import { BiRightArrowAlt } from "react-icons/bi";
import { client } from "../../client";
import "./Hero.scss";
const Hero = () => {
const [heroContent, setHeroContent] = useState([]);
useEffect(() => {
const query = '*[_type == "hero"]';
// this is giving response as array
client.fetch(query).then((data) => setHeroContent(data));
}, []);
return (
<div className="app__hero">
{heroContent.map((content,index)=>
<motion.div
className="app__hero-content-container"
whileInView={{ opacity: [0, 1], x: [500, 0] }}
transition={{ duration: 1, ease: "easeOut" }}
key={index}
>
<div className="app__hero-content">
<h2 className="heading-text">
Learn
<span className="highlighted"> wakeboarding </span>
the right way
</h2>
<p className="p-text">{content.intro}</p>
<p className="p-text">{content.description}</p>
<button className="primary-btn p-text app__flex">
{content.buttonlabel}
<BiRightArrowAlt />
</button>
</div>
</motion.div>}
</div>
);
};
export default Hero;

Related

How should I access this promise result (React, DataStore, AWS Amplify)

Following this tutorial https://www.youtube.com/watch?v=QrkkNte1onA&t=742s (2:36:48).
I am trying to access a promise that is found in useState value: dishes.
Here is my code on the third (and last useEffect) I query order DataDish (which contains the following info: quantity, orderID, orderDishDishId) with the condition that the orderID equals the order id of the order I'm returning. I set the dishes into a state with .then() but when accessing dishes the Dish data (name of the item, price, etc) is a promise. (See image below)
import {Card, Descriptions, Divider, List, Button} from 'antd';
import { useEffect, useState } from 'react';
import {useParams} from 'react-router-dom';
import {DataStore} from '#aws-amplify/datastore';
import {Order, OrderDish, User} from '../../models';
const DetailedOrder = () => {
const {id} = useParams();
const [order, setOrder] = useState(null);
const [customer, setCustomer] = useState(null);
const [dishes, setDishes] = useState([]);
useEffect(() => {
DataStore.query(Order, id).then(setOrder);
}, [id]);
useEffect(() => {
if(order?.userID) {
DataStore.query(User, order.userID).then(setCustomer);
}
}, [order?.userID]);
useEffect(() => {
if(!order?.id){
return;
}
DataStore.query(OrderDish, c => c.orderID.eq(order.id)).then(setDishes);
}, [order?.id])
console.log(dishes);
return (
<Card title={`Order #${id}` } style={{ margin: 20 }}>
<Descriptions bordered column={{ lg:1, md:1, sm:1 }}>
<Descriptions.Item label="Customer">{customer?.name}</Descriptions.Item>
<Descriptions.Item label="Customer Address">{customer?.address}</Descriptions.Item>
</Descriptions>
<Divider />
<List
dataSource={dishes}
renderItem={(dishItem) => (
<List.Item>
<div style={{fontWeight: 'bold'}}>{dishItem.Dish.name} x{dishItem.quantity}</div>
<div>${dishItem.Dish.price}</div>
</List.Item>
)}/>
<Divider />
<div style={styles.totalSumContainer}>
<h2 style={{fontWeight: '400'}}>Total:</h2>
<h2 style={styles.totalPrice}>${order?.total?.toFixed(2)}</h2>
</div>
<Divider />
<div style={styles.buttonsContainer}>
<Button block type='danger' size='large' style={styles.button}>
Decline Order
</Button>
<Button block type='primary' size='large' style={styles.button}>
Accept Order
</Button>
</div>
<Button block type='primary' size='large'>
Ready For Pickup
</Button>
</Card>
);
};
const styles = {
totalSumContainer: {
flexDirection: 'row',
display: 'flex',
},
totalPrice: {
marginLeft: 'auto',
fontWeight: 'bold',
},
buttonsContainer: {
display: 'flex',
paddingBottom: 30,
},
button: {
marginRight: 10,
marginLeft: 10,
color: 'white',
},
};
export default DetailedOrder;
The console.log(dishes) returns the folowing in the console:
I am trying to access the data found in Dish but its a promise and cant figure out how to bring it into state.
I've tried another .then() but I couldn't figure it out (just starting out with javascript). I've read up on async/await but I couldn't understand how to implement it.
Let me know if you might know but need more info. I'm happy to provide whatever is necessary. This is my first question on StackOverflow and it was much harder to formulate a 'good question' than I thought.
Vadim (The guy in the youtube video) does not encounter this problem (2:37:08). I suspect it is because of updates to amplify. But I'm not 100% sure.
Any info would be extremely appreciated.
Thanks for reading!
If you want to convert the api calls to async then you can try the following
useEffect(() => {
const fetchOrder = async () => {
const orderResponse = await DataStore.query(Order, id);
console.log('order', orderResponse);
setOrder(orderResponse)
// ideally fetch all data here to avoid unnecessary rerenders
// and check if the response user is different from previous use to prevent refetch of user
if(orderResponse?.userID) {
const customerResponse = await DataStore.query(User, orderResponse.userID);
console.log('order', customerResponse);
setCustomer(customerResponse)
}
if(!orderResponse?.id) {
const dishResponse = await DataStore.query(OrderDish, c => c.orderID.eq(orderResponse.id));
console.log('dish', dishResponse);
setDishes(dishResponse)
}
fetchOrder();
}, [id]);
I am hoping this will help you to debug to solve your issue and the write maintainable code.
Cheers

React and Sanity - Button is targeting wrong data

I am building my website portfolio using React and Sanity. This actually is my first project with React. The idea was to use sanity in order to store data that I can use on my website, such as "projects" and so far everything is going well, except for one thing: THE BUTTON IS TARGETING WRONG DATA.
The projects are divided in categories: UX/UI - React - JavaScript - University Projects - All
Everything is working fine, the tags imported from sanity's schemas allow me to categorise the projects.
Every project looks like a little card and when hovered, there is a little description as long as the button "MORE+".
HERE IS THE PROBLEM
When I click the button, there is a big window showing up where I can see what is the project about.
Right now there are two projects on sanity (let's call them A and B).
Project A is categorised as JavaScript and project B as React and UI/UX.
If I hover on project A and Click the button "MORE+", it would open project B on the big window, why is that?
This happens only when I am in the category "ALL" but I assume it doesn't happen in other categories only because there is only one project each category, while in "ALL" both projects are shown.
I leave below the code that I used for the button and how I imported this from sanity.
It may look a bit confusing and long, only because I used a lot of motion frame and wrapped everything in a lot of div
Also in few point it is still uncomplete.
import React, { useState, useEffect } from 'react';
import {AiFillEye, AiFillGithub} from 'react-icons/ai';
import {motion} from 'framer-motion';
import './Work.scss';
import { HiX } from 'react-icons/hi';
import { AppWrap } from '../../wrapper';
import {urlFor, client} from '../../client';
const Work = () => {
const [works, setWorks] = useState([]);
const [filterWork, setFilterWork] = useState([]);
const [activeFilter, setActiveFilter] = useState('All');
const [animateCard, setAnimateCard] = useState({ y: 0, opacity: 1 });
const [toggle, setToggle] = useState(false);
useEffect(() => {
const query = '*[_type == "works"]';
client.fetch(query).then((data) => {
setWorks(data);
setFilterWork(data);
});
}, []);
const handleWorkFilter = (item) => {
setActiveFilter(item);
setAnimateCard([{ y: 100, opacity: 0 }]);
setTimeout(() => {
setAnimateCard([{ y: 0, opacity: 1 }]);
if (item === 'All') {
setFilterWork(works);
} else {
setFilterWork(works.filter((work) => work.tags.includes(item)));
}
}, 500);
};
return (
<>
<h2 className="portfolio-head-text">My <span>Portfolio</span></h2>
<div className="app__work-filter">
{['UI/UX','JavaScript', 'React JS', 'University Projects', 'All'].map((item, index) => (
<div key={index}
onClick={() => handleWorkFilter(item)}
className={`app__work-filter-item app_flex p-text ${activeFilter === item ? 'item-active' : ''}`}>
{item}
</div>
))}
</div>
<motion.div
animate={animateCard}
transition={{duration:0.5, delayChildren: 0.5}}
className="app__work-portfolio"
>
{filterWork.map((work,index) => (
<div className="app__work-card-container" key={index}>
<div className="app__work-item app__flex">
<div className="app__work-img app__flex">
<img src={urlFor(work.imgUrl1)} alt={work.name}/>
<motion.div
whileHover={{opacity:[0,1]}}
transition={{duration: 0.3, ease: 'easeInOut', staggerChildren: 0.6}}
className="app__work-hover app__flex">
<p>{work.descriptionPreview}</p>
<motion.div
whileInView={{scale:1}}
whileHover={{scale:[1,0.9]}}
transition={{duration: 0.2}}
className="app__flex"
>
<button onClick={() => setToggle(true)}>more+</button>
</motion.div>
</motion.div>
</div>
<div className="app__work-content app__flex">
<h4 className="bold-text">{work.title}</h4>
<p className="p-text" style={{marginTop: 10}}>{work.tagView}</p>
</div>
</div>
{toggle &&(
<div className="app__work-big-window">
<div className="window-img-x">
<img classname="window-img" src={urlFor(work.imgUrl1)} alt={work.name}/>
<div><HiX className="window-x" onClick={() => setToggle(false)}/></div>
</div>
<div>
<h4>{work.title}</h4>
<h6>{work.subTitle}</h6>
<div/>
<p>{work.description}</p>
</div>
<div/>
<div>
<h6>Technologies used: </h6>
<p>{work.tech}</p>
</div>
</div>
)}
</div>
))}
</motion.div>
</>
)
}

How to add Google Ads in a Feed after every 'n' number of Post using next.js

I want to create a feed where a Google Ad is shown after every 10 posts just like Instagram. I am using Firebase as my database and tailwind-CSS for the styling. How would I use Google Ads to implement this feature?
Here is my code for displaying a Feed
Feed.js
import {React, useState, useEffect} from "react";
import Navbar from "./Navbar";
import Post from "./Post";
import { onSnapshot, collection, query, orderBy } from "#firebase/firestore";
import { db } from "../firebase";
function Feed() {
const [posts, setPosts] = useState([]);
useEffect(
() =>
onSnapshot(
query(collection(db, "posts"), orderBy("timestamp", "desc")),
(snapshot) => {
setPosts(snapshot.docs);
}
),
[db]
);
return (
<div>
<Navbar />
<div className="pb-72">
{posts.map((post) => (
<Post key={post.id} id={post.id} post={post.data()} />
))}
</div>
</div>
);
}
export default Feed;
The javascript map function has a second parameter - index - that tells you the index of the item in the array it is iterating. So you would want to make two key changes:
return (
<div>
<Navbar />
<div className="pb-72">
{posts.map((post, idx) => {
// If true, you're on the tenth post
const isTenthPost = (idx + 1) % 10 === 0
// Note the addition of the React fragment brackets - your map call
// has to return a single React component, so we add this to handle
// the case where we want to return both the post and the Google ad.
return (
<>
<Post key={post.id} id={post.id} post={post.data()} />
{ isTenthPost && <GoogleAdComponent /> }
</>
)
})}
</div>
</div>
);
I'm not suggesting you copy and paste this exactly, but it should help you understand how to determine if you're on the nth post and how to conditionally display another component.

Style background not showing an image

I can't seem to solve this, The URLs are not showing as images, what can I do?
I think the issue is with the style background image but I'm not sure, the name is showing fine but the image is not.
here is the code:
import React, {useState} from "react";
import TinderCard from "react-tinder-card";
function TinderCards() {
const [people, setPeople] = useState([
{
name:"sonny",
url:"https://upload.wikimedia.org/wikipedia/commons/b/b8/Lola_Astanova.jpg",
},
{
name:"danny",
url:"https://upload.wikimedia.org/wikipedia/commons/b/b8/Lola_Astanova.jpg",
},
]);
// const people = []; array
return (
<div>
<h1>cards</h1>
{people.map((person) => ( <TinderCard
className="swipe"
key={person.name}
preventSwipe={['up', 'down']}
>
<div
// eslint-disable-next-line no-template-curly-in-string
style={{ backgroundImage: 'url(${person.url})' }}
className="card">
<h3>{person.name}</h3>
</div>
</TinderCard>
))}
</div>
);
}
export default TinderCards
That's what I would do as well. Work backwards if stuck. I would think the tics would work. Not showing image equates to? Have you viewed source / what was output?

how to delete a document from firebase in firestore by using react?

this is my first react project by using firebase, everything is correct which all upload function works very well, also all images can be shown, but i use firebase.firestore.collection('image').document(doc.id).delete() when i want to delete one of the images, it shows an error which is:
Uncaught TypeError: db.document is not a function
I do not know what is going on, can someone help to resolve it, please?
projectFirestore = firebase.firestore() in the firebase config file.
import React from 'react';
import useFirestore from '../hooks/useFirestore';
import { motion } from 'framer-motion';
import { projectFirestore } from '../firebase/config';
const ImageGrid = ({ setSelectedImg }) => {
const { docs } = useFirestore('images');
const db = projectFirestore.collection('image');
return (
<div className="img-grid">
{docs &&
docs.map(doc => (
<motion.div
className="img-wrap"
key={doc.id}
layout
whileHover={{ opacity: 1 }}
onClick={() => setSelectedImg(doc.url)}
>
<motion.img
src={doc.url}
alt="uploaded pic"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 1 }}
/>
<button className='showBt' onClick={() => db.document(doc.id).delete()}>-</button>
</motion.div>
))}
</div>
);
};
export default ImageGrid;
The method you're looking for is doc(), not document().

Categories