i have been trying to understand stripe for quite a while now. the major problem i am having is that i am a front end developer (with about a year of experience) and while i have some node.js/backend experience it is simply not enough to handle server processing of payments. i am going for the JAMstack serverless function approach using netlify. and thus far everything seems to be working out EXCEPT right at redirect to checkout i am getting the error "stripe.redirectToCheckout is not a function"
here is some of my code :
const inventory = require('./data/products.json');
exports.handler = async (event) => {
const { sku, quantity } = JSON.parse(event.body);
const product = inventory.find((p) => p.sku === sku);
const validatedQuantity = quantity > 0 && quantity < 2 ? quantity : 1;
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
billing_address_collection: 'auto',
shipping_address_collection: {
allowed_countries: ['US'],
},
success_url: `${process.env.URL}/success`,
cancel_url: process.env.URL,
line_items: [
{
name: 'bitch',
currency:'USD',
amount: 299,
quantity: validatedQuantity,
},
],
});
return {
statusCode: 200,
body: JSON.stringify({
sessionId: session.id,
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY,
}),
};
};
^^^^this is where i create the checkout through a serverless function
although it took some time i have been able to create a lambda function through netlify, hide my public and private keys, create a stripe element, but i am just so confused as to why i am getting this error...
blow is where the error seems to be
//client sides
import Stripe from 'stripe'
export async function handleFormSubmission(event) {
event.preventDefault();
const form = new FormData(event.target);
const data = {
sku: form.get('sku'),
quantity: Number(form.get('quantity')),
};
const response = await fetch('/.netlify/functions/create-checkout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then((res) => res.json());
const stripe=Stripe(response.publishableKey);
const {err}=await stripe.redirectToCheckout({
sessionId:response.sessionId
})
if(err){
console.log(err)
}
}
also if it is any help here is where i am calling the function (handleformsubmit or whatever)
import React from 'react'
import {loadStripe} from "#stripe/stripe-js"
import './Checkout.css'
import {useState} from 'react'
import {Elements,
CardElement,
useStripe,
useElements} from '#stripe/react-stripe-js'
import axios from 'axios'
import {loadProducts} from './load-products'
import {handleFormSubmission} from './stripe-purchase'
const stripePromise=loadStripe(`${process.env.STRIPE_}`)
const CheckoutForm=()=>{
const stripe=useStripe()
const elements=useElements()
const handleSubmit=async(e)=>{
e.preventDefault()
if (!stripe || !elements) {
// Stripe.js has not loaded yet. Make sure to disable
// form submission until Stripe.js has loaded.
return;
}
// Get a reference to a mounted CardElement. Elements knows how
// to find your CardElement because there can only ever be one of
// each type of element.
const cardElement = elements.getElement(CardElement);
const {error, paymentMethod}=await stripe.createPaymentMethod({
type:'card',
card:cardElement
})
loadProducts();
}
return (
<form onSubmit={handleFormSubmission} method='POST'>
<img className='checkoutImage' src='./logo2.png' />
<label class="ml-2 font-bold text-blue-700 text-md">
Enter card details below:
</label>
<fieldset className="my-2 FormGroup">
<div className="FormRow">
<CardElement options={{
style: {
base: {
fontSmoothing: 'antialiased',
fontWeight: 900,
iconColor: '#60A5FA',
fontSize: '30px',
color: '#374151',
'::placeholder': {
color: '#3182ce',
},
},
invalid: {
iconColor: '#EF4444',
color: '#DC2626',
},
complete:{
iconColor:'green',
color: 'green',
}
},
}}/>
</div>
</fieldset>
<div className='checkoutbuttonContainer'>
<button type="submit" className="scoreButtons scoreButtonGrey flex justify-center rounded-md border border-gray-300 bg-pink-600 shadow-sm px-4 py-2 bg-white text-base font-medium text-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 hover:bg-pink-500 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Submit Payment <span className='priceLine'></span> <span className='price'> </span>
</button>
</div>
</form>
)
}
const StripeSetup = () => {
return (
<div><Elements stripe={stripePromise}><CheckoutForm /></Elements></div>
)
}
export default StripeSetup
this is the first time in a while i have felt really overwhelmed by new material. im not even sure if i'm learning at a proper pace anymore nor even learning as much as just copying in code i find online lol... but this did take a good amount of work on my end nonetheless. would really love if someone could help. BTW i am now realizing that i created stripe elements etc which i don't even know if are necessary when using redirect to checkout as this is supposed to lead the client to a stripe checkout? can someone clarify all this for me. and please help if they can! thanks so much in advance
ONE last thing i wanted to say. i do not need a cart, products listing or anything. this is a one time payment for 2.99 and it will lead to the next page is the user submits the payment. not sure if that changes anything but figured the more details the better
Problem was in AWFUL documentation as well as netlify's instructions.
i needed to call loadstripe again first:
const stripe=await loadStripe(response.publishableKey);
const {err}=await stripe.redirectToCheckout({
sessionId:response.sessionId
})
Related
I am creating a multistep register form in which I provide an avatar upload. Because it is a multistep form, I want to store the data in a Pinia store until the form finally gets submitted. Everything works fine so far. But I want to be able to delete the value that contains the Blob URL for the avatar, so the user can choose a different image. What I am trying to do is this userRegisterStore.cardOwner.avatar = '' cause the initial state of that value is just an empty string. But I get this error message:
runtime-core.esm-bundler.js:218 Uncaught TypeError: 'set' on proxy: trap returned falsish for property 'avatar'
I also use cropperjs and vue-cropperjs. But I think that's irrelevant in this case.
I Googled all day and found nothing. So, I hope someone here can help.
[EDIT]
I created a codesandbox.io I hope it works. The first file you should see is RegisterFormFive.vue. To view it, you need to go to this link or use the integrated preview in codesandbox: https://n9dfv3-5173.preview.csb.app/register. Then upload an image, crop it (orange button beneath the image), and then try to delete it (red button)
Here's my code:
// RegisterDataStore.js
export const useRegisterDataStore = defineStore('RegisterDataStore', {
state: () => ({
imgReady: false,
cardOwner: reactive({
firstName: '',
lastName: '',
email: '',
password: '',
agbAccepted: false,
dsgvoAccepted: false,
title: '',
companyName: '',
companyPublic: false,
position: '',
positionPublic: false,
avatar: '',
addresses: [],
contacts: [],
links: [],
}),
}),
// Cropper part
<Cropper
v-if="registerDataStore.cardOwner.avatar && !registerDataStore.imgReady"
class="mx-auto max-h-[350px] max-w-[350px] overflow-hidden rounded-lg border-2 border-skin-primary bg-skin-primary"
ref="cropper"
alt="User avatar"
drag-mode="move"
:src="registerDataStore.cardOwner.avatar"
:aspect-ratio="1 / 1"
:crop-box-movable="false"
:crop-box-resizable="false"
:auto-crop-area="0.6"
:guides="false"
:movable="true"
:scalable="true"
:zoomable="true"
:zoo-on-touch="true"
:max-canvas-width="350"
:max-canvas-height="350"
:zoom-on-wheel="true"
:rotate-on-drag="false"
:rotatable="false"
:background="false"
:modal="true"
:initial-aspect-ration="1 / 1"
:view-mode="1"
></Cropper>
// Conponent script
<script setup>
import HeaderNav from '#/components/HeaderNav.vue'
import HeaderTitle from '#/components/HeaderTitle.vue'
import { useRegisterDataStore } from '#/stores/RegisterDataStore'
import Cropper from 'vue-cropperjs'
import 'cropperjs/dist/cropper.css'
import { ref } from 'vue'
import { useObjectUrl } from '#vueuse/core'
name: 'RegisterFormFive'
const registerDataStore = useRegisterDataStore()
const avatarInput = ref(null)
const cropper = ref(null)
const fileChanged = (event) => {
const file = event.target.files[0] || e.dataTrtansfer.files[0]
const reader = new FileReader()
reader.onload = (e) => {
registerDataStore.cardOwner.avatar = e.target.result
}
reader.readAsDataURL(file)
}
const deleteAvatar = (event) => {
registerDataStore.cardOwner.avatar = null
registerDataStore.imgReady = false
}
</script>
// The button that tiggers the storage
<div class="mt-4 flex justify-center">
<button
v-if="!registerDataStore.imgReady"
#click.prevent="
cropper.getCroppedCanvas().toBlob((blob) => {
registerDataStore.cardOwner.avatar = useObjectUrl(blob)
registerDataStore.imgReady = true
})
"
type="button"
class="hover:bg-skin-primary-dark inline-flex items-center rounded-md border border-transparent bg-skin-primary px-4 py-2 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-skin-primary focus:ring-offset-2"
>
// The file input field
<input
type="file"
ref="avatarInput"
accept=".jpg,.png"
#change="fileChanged"
:style="{ display: 'none' }"
/>
// The button that should "delete" the value
<button
v-if="registerDataStore.imgReady"
#click.prevent="deleteAvatar"
type="button"
class="hover:bg-skin-primary-dark inline-flex items-center rounded-md border border-transparent bg-red-700 px-4 py-2 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-skin-primary focus:ring-offset-2"
>
<IconWarning
class="mr-2 h-5 w-5 fill-current text-skin-primary"
aria-hidden="true"
/>
Bild löschen
</button>
Nested reactive isn't needed in state, Pinia state is already reactive. #click.prevent handler doesn't need to be created in a template, it doesn't affect how it works but makes debugging harder.
VueUse useObjectUrl composable is the problem. Due to how Vue reactive API works, refs are unwrapped inside reactive object. Since useObjectUrl returns readonly ref, it makes cardOwner.avatar property readonly and prevents from reassigning a value. Changing it would require the whole object to be reassigned:
registerDataStore.cardOwner = { ...registerDataStore.cardOwner, avatar: ... }
The actual problem is that useObjectUrl is misused. Since blob value doesn't change in the scope of then function, it can't benefit from being reactive. The composable should be replaced with the actual thing that it does:
registerDataStore.cardOwner.avatar = URL.createObjectURL(newObject)
I was following a tutorial on youtube (https://youtu.be/3HNyXCPDQ7Q) for creating a portfolio website. I hosted the website using Netlify, 20 days later when I revisited the website, the website was just a blank screen. When I tested again on localhost, the problem was with sanity. When I connected to sanity, the screen would go blank.
Now the problem is that the regular website content is visible, but the data from sanity is not being fetched to the react app.
I have added some documents in the abouts schema via the sanity gui.
Abouts Schema:
export default {
name: "abouts",
title: "Abouts",
type: "document",
fields: [
{
name: "title",
title: "Title",
type: "string",
},
{
name: "description",
title: "Description",
type: "string",
},
{
name: "imgUrl",
title: "ImgUrl",
type: "image",
options: {
hotspot: true,
},
},
],
};
About.jsx code:
import React, { useState, useEffect } from "react";
import { motion } from "framer-motion";
import "./About.scss";
import { urlFor, client } from "../../Client";
import { AppWrapper } from "../../wrapper/";
const About = () => {
const [abouts, setAbouts] = useState([]);
const querySelector = async () => {
const query = '*[_type == "abouts"]';
const aboutsQuery = await client.fetch(query);
aboutsQuery.then((data) => setAbouts(data));
};
useEffect(() => {
querySelector();
}, []);
return (
<>
<motion.div
className="app__about-header"
whileInView={{ x: [1000, 0] }}
transition={{ duration: 1 }}
viewport={{ once: true }}
>
<h1 className="head-text">
<span>About</span> Me
</h1>
</motion.div>
<motion.div
className="app__about-desc"
whileInView={{ opacity: [0, 1] }}
transition={{ duration: 1 }}
viewport={{ once: true }}
>
<h3 style={{ marginBottom: 10 }}>Who I am?</h3>
<p className="p-text">
Some text here.
</p>
</motion.div>
<motion.div
style={{ marginTop: 40 }}
whileInView={{ x: [-1000, 0] }}
transition={{ duration: 1 }}
viewport={{ once: true }}
>
<h2 className="head-text">
What I <span>Love to do?</span>
</h2>
</motion.div>
<div className="app__profiles">
{abouts.map((about, index) => {
return (
<motion.div
whileInView={{ opacity: [0, 1] }}
whileHover={{ scale: 1.1 }}
transition={{ duration: 1, type: "tween" }}
className="app__profile-item"
key={index}
viewport={{ once: true }}
>
<img src={urlFor(about.imgUrl)} alt={about.title} />
<h2 className="bold-text" style={{ marginTop: 20 }}>
{about.title}
</h2>
<p className="p-text">{about.description}</p>
</motion.div>
);
})}
</div>
</>
);
};
export default AppWrapper(About, "about", "app__whitebg");
This Client.js file will connect to the sanity CMS.
Client.js code:
import SanityClient from "#sanity/client";
import imageUrlBuilder from "#sanity/image-url";
export const client = SanityClient({
projectId: "hard coded value added here",
dataset: "portfoliodataset",
apiVersion: "2022-08-11",
useCdn: true,
token: "token value here",
});
const builder = imageUrlBuilder(client);
export const urlFor = (source) => builder.image(source);
I have tried the env variable as well in client.js file.
for eg. projectId: process.env.REACT_APP_SANITY_PROJECT_ID
and I have tried the hard coded values as well. Both don't seem to work.
Note that I have also added the localhost:3000 and the website url in the CORS origin.
Please help me, I am stuck on this problem for a few days now.
I dont know whether you are struck in this or not but giving you reply in case in future if someone struck at this point they can fix it. I also faced the same issue and strucked for the time being and later I realized the issue. The issue is you can't give some random name to the dataset
export const client = SanityClient({
projectId: "hard coded value added here",
dataset: "portfoliodataset",
apiVersion: "2022-08-11",
useCdn: true,
token: "token value here",
});
Here in this dataset field you have to give the dataset name which is in sanity.json file. Hope it would help you
I was facing same issue and I was able to fix it by doing following stuff:
Installing dotenv node package
Moving .env file to frontend_react folder. (I accidentally created it under src folder)
I hope that it would help you too.
Make sure you have import the key and run the sanity client on your localhost
I had exactly the same problem, in the same tutorial. make sure first you have internet connection, then try restarting everything even with internet on.
Its basically a network issue not withstanding any other possible cause of error, but you could give it time then later refresh the react app as well as the sanity client.
Also you could try adding some more code to give the compiler something new to compile, some how the app will load sanity.
Check your sanity.json or sanity.config.js file (if you are using vite) and make sure the dataset you are using is the correct one. It has to match what you have in the client.js file
I had the same issue
I am making a website using Next.js and the above error is shown every time.
Don't know what is wrong in my code.
const Login = () => {
const [userMsg, setUserMsg] = useState("");
const [email, setEmail] = useState("");
const router=useRouter();
const handleOnChangeEmail = (e) => {
e.preventDefault();
setUserMsg("");
console.log("event", e);
const email = e.target.value;
setEmail(email);
};
const handleLoginWithEmail = (e) => {
e.preventDefault();
if (email) {
if (IsEmail.validate(email)){
router.push("/")
}else{
setUserMsg("Enter a valid email address")
}
} else {
//show usermssg
setUserMsg("Enter an email address");
}
};
return (
<div className="bg-[url('/static/bglg.jpg')] flex items-stretch flex-col h-screen w-full">
<head>
<title>NeoVest SignIn</title>
</head>
<header className="text-4xl px-10 py-2 font-black">
<span className="text-indigo-700">NeoVest</span>
</header>
<div className="w-full max-w-xs m-auto bg-[#C9C9C9] rounded p-5 bg-opacity-50 border-gray-200">
<header>
<div className="text-indigo-700 font-black text-3xl py-2">
<p>Sign In</p>
</div>
</header>
<form className="py-5">
<div>
<label className="block mb-2 text-indigo-500" for="username">
Email
</label>
<input
className="w-full p-2 mb-6 text-indigo-700 border-b-2 border-indigo-500 outline-none focus:bg-gray-300"
type="text"
name="username"
placeholder="Email Address"
onChange={handleOnChangeEmail}
/>
<div className="block mb-2 text-red-700">
<p>{userMsg}</p>
</div>
</div>
<div>
<input
className="w-full bg-indigo-700 hover:bg-pink-700 text-white font-bold py-2 px-4 mb-6 rounded"
type="button"
value="Submit"
onClick={handleLoginWithEmail}
/>
</div>
</form>
</div>
</div>
);
};
Another error shown is due to some suspense boundary causing root to switch to client side rendering :
Error: There was an error while hydrating. Because the error happened
outside of a Suspense boundary, the entire root will switch to client
rendering.
I am also using Tailwind if that information is important.
If you would look at the console you would see a warning.
Warning: validateDOMNesting(...): <head> cannot appear as a child of <div>
So, to fix this you just have to move head out of the div and move it to a different higher component.
if you have this Warning in chrome:
> `validateDOMNesting(...): <head> cannot appear as a child of <div>`
Your DOM Tree is not printed in the browser correctly, one or more tags are not closed properly.
The problem is this:
<head>
<title>NeoVest SignIn</title>
</head>
To solve it, first import Head, and then, use it with Capital "H"
import Head from "next/head"
<Head>
<title>NeoVest SignIn</title>
</Head>
I verified some topics in my code. First, verified if in next.config.js was styled components declared.
reactStrictMode: true,
compiler: {
styledComponents: true,
},
In second, I checked if file babel ".babelrc" was created in root folder with this content:
{
"presets": ["next/babel"],
"plugins": ["styled-components"]
}
The error yet showed. Component by component I checked and more two errors was found. First, HTML errors like this:
<!-- wrong -->
<p>
<ul></ul>
</p>
<!-- right -->
<p></p>
<ul></ul>
<p></p>
<!-- or right -->
<div>
<ul></ul>
</div>
Finally, I found an error in initializing component. In my case, I declared - incorrectly - a const items (object). The right way to execute this is using useState.
<!-- WRONG WAY THAT I WAS DID -->
const Works = () => {
const items = [
{
id: 1,
name: 'any name',
src: 'srcpath'
},
{
id: 2,
name: 'any name 2',
src: 'srcpath'
},
{
id: 3,
name: 'any name 3',
src: 'srcpath'
},
];
return(
items.map((item, index) => {
return(
<div key={index}>{item.name}</div>
)
}
)
});
<!-- RIGHT WAY THAT I FIXED -->
const Works = () => {
const [ item, setItem ] = React.useState();
React.useEffect(() => {
setItem([
{
id: 1,
name: 'any name',
src: 'srcpath'
},
{
id: 2,
name: 'any name 2',
src: 'srcpath'
},
{
id: 3,
name: 'any name 3',
src: 'srcpath'
},
]);
}, []);
if(item)
return(
items.map((item, index) => {
return(
<div key={index}>{item.name}</div>
)
}
)
});
Time and randomness are two of the things that most commonly produce
this
as discussed in Hydration errors - Text content does not match server-rendered HTML. #38263
Possible solution might be to use useEffect hook
// Example component with an error
import React from "react";
const AllNames = ["Ali", "Elisa", "Bella", "Carmen"];
const RandomUniqueNames = Array.from({ length: 4 }).map((_, index) => {
let randomIndex = Math.floor(Math.random() * AllNames.length);
console.log(randomIndex);
let name = AllNames[randomIndex];
return {
name,
};
});
export default function ModernNames() {
return (
<div>
<h1>ModernNames</h1>
{RandomUniqueNames.map((name) => (
<p>{name.name}</p>
))}
</div>
);
}
// Example solution
import React, { useState, useEffect } from "react";
const AllNames = ["Ali", "Elisa", "Bella", "Carmen"];
export default function ModernNames() {
const [randomNames, setRandomNames] = useState([]);
useEffect(() => {
const RandomUniqueNames = Array.from({ length: 4 }).map((_, index) => {
let randomIndex = Math.floor(Math.random() * AllNames.length);
console.log(randomIndex);
let name = AllNames[randomIndex];
return {
name,
};
});
setRandomNames(RandomUniqueNames);
}, []);
return (
<div>
<h1>ModernNames</h1>
{randomNames.map((name, index) => (
<p key={index}>{name.name}</p>
))}
</div>
);
}
You can try downgrading react version. Try react#17.
I am a beginner who wants to build a blog using Notion API, Next.js and Tailwind CSS. I learned the following code from here: https://egghead.io/lessons/next-js-request-notion-database-data-from-the-api-with-next-js.
The following code works fine in /post/index.js, but I get this error when I put the following code into /components/PostTest.js and import it in /index.js.
How do I solve this problem?
Error information
error screenshot
Server Error
TypeError: Cannot read properties of undefined (reading 'map')
#line 9
return posts.map((posts) => (
Source Code
import Head from "next/head";
import Link from "next/link";
import { Client } from "#notionhq/client";
import { useState } from "react";
export const PostPage = ({ posts }) => {
const [post] = useState(null);
return posts.map((posts) => (
<div className="bg-[#F5F5F7] dark:bg-black px-4 py-2 md:py-4">
<div className="bg-[#FFFFFF] dark:bg-[#141414] max-w-sm rounded-xl overflow-hidden shadow-sm container mx-auto">
<img
className="aspect-[16/9] bg-cover bg-center"
src={posts.coverImage}
alt="Post Banner"
/>
<div className="px-6 py-4">
<p className="text-[12px] md:text-[14px] dark:text-[#888888] leading-5 font-[700] pt-2 uppercase tracking-normal mb-[8px]">
{posts.Category}
</p>
<Link href={`/post/${posts.PID}`}>
<div className="text-lg md:text-xl text-[#1d1d1f] dark:text-[#F5F5F7] leading-snug font-[700]">
{posts.Title}
</div>
</Link>
<p className="text-[14px] text-[#6e6e73] dark:text-[#888888] leading-5 font-[600] pt-2">
{new Date(posts.Date).toLocaleDateString()}
</p>
</div>
</div>
</div>
));
};
export const getStaticProps = async () => {
const notion = new Client({
auth: process.env.NOTION_TOKEN,
});
// get posts more than 100 pages.
let results = [];
let data = await notion.databases.query({
database_id: process.env.NOTION_POST_DATABASE_ID,
filter: {
property: "Status",
select: {
equals: "Published",
},
},
sorts: [
{
property: "Date",
direction: "descending",
},
],
});
results = [...data.results];
while (data.has_more) {
data = await notion.databases.query({
database_id: process.env.NOTION_POST_DATABASE_ID,
filter: {
property: "Status",
select: {
equals: "Published",
},
},
start_cursor: data.next_cursor,
});
results = [...results, ...data.results];
}
const posts = results.map((post) => ({
id: post.id,
Title: post.properties.Title.title[0].text.content,
Category: post.properties.Category.select.name,
category_color: post.properties.Category.select.color,
Date: post.properties.Date.date.start,
Tags: post.properties.Tags.multi_select.map((Tags) => Tags.name),
Tags_color: post.properties.Tags.multi_select.map((TagsColor) => TagsColor.color),
PID: post.properties.PID.rich_text[0].text.content,
Author: post.properties.Author.people.map((people) => people.name),
Author_avatar_url: post.properties.Author.people.map((people) => people.avatar_url),
coverImage:
post.cover.file?.url ||
post.cover.external?.url,
}));
return {
props: {
posts,
},
revalidate: 1,
};
};
export default PostPage;
First of all, post should be inited with an empty array:
const [post, ] = useState([]);
Secondly, you cannot return an array of JSX, so wrap it in a Fragment or <>.
return (
<>
posts.map((posts) => (
...
)
</>
)
if posts is null or it's not an array, you'll get that error.
Try this fix
return (
posts?.map((posts) => (
...
)
)
I am new to Vuejs and would need some help, please. I am calling a component in order to create a 'member'. While calling this component and passing the member data, the component raises an error related to one of the functions inside. The issue seems that the member data is not being initially fulfilled considering that the member data is being fetched via an API. I have tried converting the function which causes the error to Asynchronous but that does not seem to work either. Below please find some code snippets for my issue.
Home.vue
<Member :member="memb" />
data() {
return {
memb: {},
};
},
//Method fetching the member from api
async fetchMember(memberId) {
const res = await axios.get(`http://localhost:3000/members/${memberId}`, { apiHeaders });
return res.data;
},
//Method which fetched the member
async created() {
this.memb = await this.fetchMember(1);
},
Member.vue
<template>
<div class="m-auto">
<p>Name: {{ member.name }}</p>
<p>Surname: {{ member.surname }}</p>
<p>Date of Birth: {{ formatDob(member.dob) }}</p>
<p>Age: {{ calculateAge(member.dob) }}</p>
<p>Relationship: {{ member.relationship }}</p>
<button class="bg-blue-500 py-1 px-4 rounded-md text-gray-100 text-sm mx-1 mt-3 font-bold" #click="$emit('get-member-id', member.id)">Update</button>
<button class="bg-red-500 py-1 px-4 rounded-md text-gray-100 text-sm mx-1 mt-3 font-bold" #click="$emit('delete-member', member.id)">Delete</button>
</div>
</template>
<script>
export default {
name: 'Member',
props: {
member: Object,
},
methods: {
// Converting the date of birth format
formatDob(dob) {
return dob.split('/').reverse().join('/');
},
// Calculating the date of birth by comparing today's date with the member's date of birth
calculateAge(dob) {
const birthdate = new Date(dob);
const todayDate = new Date();
const difference = todayDate - birthdate; // This is the difference in milliseconds
return Math.floor(difference / 31557600000);
},
},
};
</script>
The issue that I am getting is related to calling the formatDOB from the Member component. In fact the issue says: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'split')
Thanks in advance.
Will be cool if we can implement a progress indicator meant for informing the users about loading activity of data
Once the member data loads, you can start showing the member component and hide the progress indicator.
Pseudocode would look like something below.
<Member v-if="isLoaded" :member="memb" />
<ProgressBar v-if="!isLoaded" />
<script>
export default {
data: (() => {
return {
isLoaded: false,
memb: null
}
})
async mounted() {
this.isLoaded = false;
this.memb = await this.fetchMember(1);
this.isLoaded = true;
}
}
</script>