Why can I not type into the input fields using Formik and Yup? - input

When I am trying to type into the input fields there are no letters appearing. I actually have a login page using similar code working fine, so I have no clue right now why it is not working on this one. I thought it might have something to do with the way I defined the initialValues, because they are nested, but I already tried to write them not nested and it did not work.
import { NextPage } from 'next'
import { Prisma } from '#prisma/client';
import { FormikValues, useFormik, validateYupSchema } from 'formik';
import * as Yup from 'yup'
import { useState } from 'react';
import { useRouter } from 'next/router';
import { useSession } from 'next-auth/react';
const Register: NextPage = () => {
const router = useRouter()
const { data: session } = useSession()
if (session) {
router.push('/')
}
const [error, setError] = useState('')
const handleRegister = async ({
userInfo: {
email,
password
},
companyInfo: {
companyName,
gender,
firstName,
lastName,
street,
houseNumber,
postcode,
city,
country,
countryCode,
callNumber,
emailAddress,
website,
socials
} }: FormikValues) => {
const userInfo: Prisma.UserCreateInput = {
email,
hashedPassword: password //Not hashed yet!
}
const companyInfo: Prisma.CompanyCreateInput = {
name: companyName,
gender,
firstName,
lastName,
street,
houseNumber,
postcode,
city,
country,
countryCode,
callNumber,
emailAddress,
website,
socials,
companyUser: {
connect: { id: '' }
}
}
const registerData: FormikValues = {
userInfo,
companyInfo
}
const registerDataJSON = JSON.stringify(registerData)
const endpoint: RequestInfo = '/api/register/register'
const options: RequestInit = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: registerDataJSON
}
try {
const response: Response = await fetch(endpoint, options)
const url = response.headers.get('Location')
if (url) {
window.location.href = url
}
} catch {
setError('Register failed')
} finally {
formik.setSubmitting(false)
}
}
const formik = useFormik({
initialValues: {
userInfo: {
email: '',
password: ''
},
companyInfo: {
companyName: '',
gender: '',
firstName: '',
lastName: '',
street: '',
houseNumber: '',
postcode: '',
city: '',
country: '',
countryCode: '',
callNumber: '',
emailAddress: '',
website: '',
socials: ''
}
},
validationSchema: Yup.object().shape({
userInfo: Yup.object().shape({
email: Yup.string()
.required('email address is required')
.email('email address must be valid')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Email Address cannot include leading and trailing spaces'),
password: Yup.string()
.required('Password address is required')
.min(8, 'Password must be at least 8 characters long')
.max(20, 'Password cannot be longer than 20 characters')
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!##\$%\^&\*])/,
'Password must contain at least one number as well as one uppercase, lowercase and special character'
)
.trim('Password cannot include leading and trailing spaces')
.matches(
/^(?!.* )/,
'Password cannot contain whitespaces'
)
}),
companyInfo: Yup.object().shape({
companyName: Yup.string()
.strict(true)
.required()
.max(50, 'Company Name cannot be longer than 50 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Company Name cannot include leading and trailing spaces'),
gender: Yup.string()
.strict(true)
.required()
.max(20, 'Gender cannot be longer than 20 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Gender cannot include leading and trailing spaces'),
firstName: Yup.string()
.strict(true)
.required()
.max(50, 'First Name cannot be longer than 50 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('First Name cannot include leading and trailing spaces'),
lastName: Yup.string()
.strict(true)
.required()
.max(50, 'Last Name cannot be longer than 50 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Last Name cannot include leading and trailing spaces'),
street: Yup.string()
.strict(true)
.required()
.max(50, 'Street cannot be longer than 50 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Street cannot include leading and trailing spaces'),
houseNumber: Yup.string()
.strict(true)
.required()
.max(10, 'House Number cannot be longer than 10 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Hpuse Number cannot include leading and trailing spaces'),
postcode: Yup.string()
.strict(true)
.required()
.max(50, 'Postcode cannot be longer than 50 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Postcode cannot include leading and trailing spaces'),
city: Yup.string()
.strict(true)
.required()
.max(58, 'City cannot be longer than 58 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('City cannot include leading and trailing spaces'),
country: Yup.string()
.strict(true)
.required()
.max(56, 'Country cannot be longer than 56 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Country cannot include leading and trailing spaces'),
countryCode: Yup.string()
.strict(true)
.required()
.max(10, 'Country Code cannot be longer than 10 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Country Code cannot include leading and trailing spaces')
.matches(
/^(?!.* )/,
'Country Code cannot contain whitespaces'
),
callNumber: Yup.string()
.strict(true)
.required()
.min(1, 'Call Number must be bigger than 0')
.max(20, 'Call Number cannot be longer than 20 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Country Code cannot include leading and trailing spaces')
.matches(
/^(?!.* )/,
'Call Number cannot contain whitespaces'
),
emailAddress: Yup.string()
.strict(true)
.required()
.email('Email Address must be valid')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Email Address cannot include leading and trailing spaces')
.matches(
/^(?!.* )/,
'Email Address cannot contain whitespaces'
),
website: Yup.string()
.strict(true)
.required()
.url('Website must be a URL')
.max(100, 'Website cannot be longer than 100 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Website cannot include leading and trailing spaces')
.matches(
/^(?!.* )/,
'Website cannot contain whitespaces'
),
socials: Yup.string()
.strict(true)
.required()
.max(100, 'Socials cannot be longer than 100 characters')
.transform((value) => value.replace(/\s+/g, ' ').trim())
.trim('Socials cannot include leading and trailing spaces')
})
}),
onSubmit: (values) => {
handleRegister(values)
}
})
return (
<form onSubmit={formik.handleSubmit} noValidate>
<h2>User Information</h2>
<label>
Email Address<span>*</span>
<input
name='email'
type='email'
value={formik.values.userInfo.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.userInfo?.email && formik.errors.userInfo?.email && <p>{formik.errors.userInfo.email}</p>}
<label>
Password<span>*</span>
<input
name='password'
type='password'
value={formik.values.userInfo.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.userInfo?.password && formik.errors.userInfo?.password && <p>{formik.errors.userInfo.password}</p>}
<h2>Company Information</h2>
<label>
Company Name<span>*</span>
<input
name='companyName'
type='text'
value={formik.values.companyInfo.companyName}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.companyName && formik.errors.companyInfo?.companyName && <p>{formik.errors.companyInfo.companyName}</p>}
<label>
Gender<span>*</span>
<input
name='gender'
type='text'
list='genders'
value={formik.values.companyInfo.gender}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
<datalist id='genders'>
<option>Female</option>
<option>Male</option>
</datalist>
</label>
{formik.touched.companyInfo?.gender && formik.errors.companyInfo?.gender && <p>{formik.errors.companyInfo.gender}</p>}
<label>
First Name<span>*</span>
<input
name='firstName'
type='text'
value={formik.values.companyInfo.firstName}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.firstName && formik.errors.companyInfo?.firstName && <p>{formik.errors.companyInfo.firstName}</p>}
<label>
Last Name<span>*</span>
<input
name='lastName'
type='text'
value={formik.values.companyInfo.lastName}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.lastName && formik.errors.companyInfo?.lastName && <p>{formik.errors.companyInfo.lastName}</p>}
<label>
Street<span>*</span>
<input
name='street'
type='text'
value={formik.values.companyInfo.street}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.street && formik.errors.companyInfo?.street && <p>{formik.errors.companyInfo.street}</p>}
<label>
House Number<span>*</span>
<input
name='houseNumber'
type='text'
value={formik.values.companyInfo.houseNumber}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.houseNumber && formik.errors.companyInfo?.houseNumber && <p>{formik.errors.companyInfo.houseNumber}</p>}
<label>
Postcode<span>*</span>
<input
name='postcode'
type='text'
value={formik.values.companyInfo.postcode}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.postcode && formik.errors.companyInfo?.postcode && <p>{formik.errors.companyInfo.postcode}</p>}
<label>
City<span>*</span>
<input
name='city'
type='text'
value={formik.values.companyInfo.city}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.city && formik.errors.companyInfo?.city && <p>{formik.errors.companyInfo.city}</p>}
<label>
Country<span>*</span>
<input
name='country'
type='text'
value={formik.values.companyInfo.country}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.country && formik.errors.companyInfo?.country && <p>{formik.errors.companyInfo.country}</p>}
<label>
Country Code<span>*</span>
<input
name='countryCode'
type='text'
value={formik.values.companyInfo.countryCode}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.countryCode && formik.errors.companyInfo?.countryCode && <p>{formik.errors.companyInfo.countryCode}</p>}
<label>
Call Number<span>*</span>
<input
name='callNumber'
type='text'
value={formik.values.companyInfo.callNumber}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.callNumber && formik.errors.companyInfo?.callNumber && <p>{formik.errors.companyInfo.callNumber}</p>}
<label>
Email Address<span>*</span>
<input
name='emailAddress'
type='email'
value={formik.values.companyInfo.emailAddress}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.emailAddress && formik.errors.companyInfo?.emailAddress && <p>{formik.errors.companyInfo.emailAddress}</p>}
<label>
Website
<input
name='website'
type='text'
value={formik.values.companyInfo.website}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.website && formik.errors.companyInfo?.website && <p>{formik.errors.companyInfo.website}</p>}
<label>
Socials
<input
name='socials'
type='text'
value={formik.values.companyInfo.socials}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
</label>
{formik.touched.companyInfo?.socials && formik.errors.companyInfo?.socials && <p>{formik.errors.companyInfo.socials}</p>}
<button type='submit' disabled={formik.isSubmitting}>Login</button>
{formik.isSubmitting && <div>Loading...</div>}
{!formik.isSubmitting && error && <div>{error}</div>}
</form>
)
}
export default Register

When you have nested initialValues, the name of the input must represent the property of the corresponding object. Regarding your example you have to change the current name of the input ('email') to 'userInfo.email' as showcased below:
<input
name="userInfo.email"
type="email"
value={formik.values.userInfo.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>

Related

NextJS Mailchimp API Error Handling and useState

thanks a lot for taking a look at my problem.
I've been building a button to handle a Mailchimp API call but I can't seem to be able to piece it all together using useState. I would really appreciate some help with this as it is currently throwing 500 errors when successfully added to the mailing list with new email addresses and the response from Mailchimp seems to be fine.
I know there is probably some sort of react hook or other shortcut I could use but I really want to make this work..! hahaa!
api/subscribe.js
export default function handler(req, res) {
const { email } = req.body;
console.log(`email: ${email}`);
if (!email) {
return res.status(400).json({ error: 'Email is required' });
}
try {
const client = require('#mailchimp/mailchimp_marketing');
const API_KEY = process.env.MAILCHIMP_API_KEY;
const API_SERVER = process.env.MAILCHIMP_API_SERVER;
const LIST_ID = process.env.MAILCHIMP_AUDIENCE_ID;
client.setConfig({
apiKey: API_KEY,
server: API_SERVER,
});
const run = async () => {
const response = await client.lists.addListMember(LIST_ID, {
email_address: `${email}`,
status: 'pending',
skip_merge_validation: true,
});
console.log(response);
};
run();
if (response.status >= 400) {
return res.status(400).json({
error: `There was an error subscribing your email address to the newsletter.
Please contact us directly so we can add you in manually. Sorry for any inconvenience.`,
});
}
return res.status(201).json({
message: 'Success! 🎉 You are now subscribed to the newsletter.',
});
} catch (err) {
console.log(res.error);
return res.status(500).json({ error: '***ERROR***' });
}
}
My messy but functional code..!
components/Contact.js
import React, { useState } from 'react';
import LoadingSpinner from '../components/LoadingSpinner';
import { CountdownCircleTimer } from 'react-countdown-circle-timer';
export default function Contact() {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
// const [loading, setLoading] = useState(true);
const [state, setState] = useState('IDLE');
const subscribe = async (e) => {
e.preventDefault();
setState('LOADING');
setMessage(null);
try {
const res = await fetch('/api/subscribe', {
body: JSON.stringify({
email: email,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
console.log(res);
if (res.status >= 400) {
console.log(res.error);
setState('ERROR');
setMessage(
<div>
An error has occurred, please{' '}
<a
href="/contact"
target="_blank"
rel="noreferrer"
className="text-blue-500 underline cursor-pointer transition-transform duration-7000 hover:text-gray-200"
onClick={() => {
setState('IDLE');
setMessage(null);
setEmail('');
}}
>
contact us
</a>
</div>
);
setEmail('');
setTimeout(() => {
setState('IDLE');
setMessage(null);
}, 10000);
return;
}
setState('SUCCESS');
setMessage('Success! 🎉 You are now subscribed to the newsletter.');
setEmail('');
setTimeout(() => {
setState('IDLE');
setMessage(null);
}, 10000);
return;
} catch (e) {
setState('ERROR');
console.log(e.res.error);
setMessage(e.res.error);
setEmail('');
setTimeout(() => {
setState('IDLE');
setMessage(null);
}, 10000);
return;
}
};
return (
<div className="h-[15rem] xs:h-[13.5rem] xsm:h-[12.5rem] sm:h-[13rem] w-full bg-black/90 px-2">
<div className="flex justify-center items-center">
<h1 className="text-center text-zinc-300 text-lg md:text-2xl mt-4">
Sign up to the Newsletter...
</h1>
</div>
<div className="flex justify-center items-center w-full">
<form onSubmit={subscribe}>
<input
type="email"
id="email-input"
name="email"
placeholder="Your#email.here"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
autoCapitalize="off"
autoCorrect="off"
aria-autocomplete="list"
className="h-[40px] min-w-[270px] md:min-w-[400px] border border-black/60 rounded-xl w-full text-center text-md px-2"
/>
<div className="flex justify-center items-center py-2">
{state === 'IDLE' && (
<button
type="submit"
value=""
name="subscribe"
disabled={state === 'LOADING'}
className="w-[200px] h-[40px] px-12 mt-2 text-lg border border-black/60 rounded-xl bg-gray-200 text-gray-900 transition-colors duration-700 transform hover:bg-gray-500 hover:text-gray-100"
>
Subscribe
</button>
)}
{state === 'ERROR' && (
<div className="h-[50px]">
<CountdownCircleTimer
isPlaying
size={55}
strokeWidth={2}
duration={10}
trailColor="#3399ff"
rotation="clockwise"
colors="#ffffff"
>
{({ remainingTime }) => remainingTime}
</CountdownCircleTimer>
</div>
)}
{state === 'SUCCESS' && (
<div className="h-[50px]">
<CountdownCircleTimer
isPlaying
size={50}
duration={10}
colors={[
['#004777', 0.33],
['#F7B801', 0.33],
['#A30000', 0.33],
]}
>
{({ remainingTime }) => remainingTime}
</CountdownCircleTimer>
</div>
)}
{state === 'LOADING' && (
<button
type="submit"
value=""
disabled
name="subscribe"
className="w-[200px] h-[40px] px-12 mt-2 text-lg border border-black/60 rounded-xl bg-gray-200 text-gray-900 transition-colors duration-700 transform"
>
<div className="flex justify-center items-center">
<LoadingSpinner />
</div>
</button>
)}
</div>
<div className="text-zinc-300 text-sm md:text-lg text-center md:min-w-[35ch]">
{message
? message
: `We only send emails when we have genuine news.`}
</div>
</form>
</div>
</div>
);
}
response from Mailchimp (with sensitive info taken out)
email: becir28216#jernang.com
undefined
{
id: '',
email_address: 'becir28216#jernang.com',
unique_email_id: '',
contact_id: '',
full_name: '',
web_id: ,
email_type: 'html',
status: 'pending',
consents_to_one_to_one_messaging: true,
merge_fields: { FNAME: '', LNAME: '' },
stats: { avg_open_rate: 0, avg_click_rate: 0 },
ip_signup: '',
timestamp_signup: '2022-11-15T20:46:33+00:00',
ip_opt: '',
timestamp_opt: '',
member_rating: 2,
last_changed: '2022-11-15T20:46:33+00:00',
language: '',
vip: false,
email_client: '',
location: {
latitude: 0,
longitude: 0,
gmtoff: 0,
dstoff: 0,
country_code: '',
timezone: '',
region: ''
},
source: 'API - Generic',
tags_count: 0,
tags: [],
list_id: '',
...}
I want to change the state of the button according to the response from the API call. I have been trying all sorts of combinations and now my head hurts..
Expected behaviour:
When it is idle, button says Subscribe and the normal message is displayed below it.
when it is loading, the button displays the loading spinner and still displays the normal message
When there is an error, the button displays the countdowntimer and the error message.
When the API call is a success, the loading spinner disappears, the success message is displayed and then 10 seconds later, the whole form resets again.

How to update data in input after loading data from onSSR?

I'm trying to load my shipping data from server (onSSR) and update to my input form. But it don't update even I put my code in onSSR like:
onSSR(async () => {
await load();
await loadCountries();
if (shipping !== undefined) {
form.firstName = shipping.value.firstName;
}
});
Please help.
<template>
<ValidationObserver v-slot='{ handleSubmit }'>
<SfHeading
:level='3'
:title="$t('Shipping')"
class='sf-heading--left sf-heading--no-underline title'
/>
<div v-if='shipping!=null'>{{ shipping.firstName }}</div>
<form #submit.prevent='handleSubmit(handleFormSubmit)'>
<div class='form'>
<ValidationProvider
v-if='!isAuthenticated'
name='email'
rules='required|email'
v-slot='{ errors }'
slim
>
<SfInput
v-model='form.email'
label='Email'
name='email'
class='form__element'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
/>
</ValidationProvider>
<ValidationProvider
name='firstName'
rules='required|min:2'
v-slot='{ errors }'
slim
>
<SfInput
v-model='form.firstName'
label='First name'
name='firstName'
class='form__element form__element--half'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
/>
</ValidationProvider>
<ValidationProvider
name='lastName'
rules='required|min:2'
v-slot='{ errors }'
slim
>
<SfInput
v-model='form.lastName'
label='Last name'
name='lastName'
class='form__element form__element--half form__element--half-even'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
/>
</ValidationProvider>
<ValidationProvider
name='streetName'
rules='required|min:2'
v-slot='{ errors }'
slim
>
<SfInput
v-model='form.addressLine1'
label='Street name'
name='streetName'
class='form__element form__element--half'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
/>
</ValidationProvider>
<ValidationProvider
name='apartment'
rules='required|min:2'
v-slot='{ errors }'
slim
>
<SfInput
v-model='form.addressLine2'
label='House/Apartment number'
name='apartment'
class='form__element form__element--half form__element--half-even'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
/>
</ValidationProvider>
<ValidationProvider
name='city'
rules='required|min:2'
v-slot='{ errors }'
slim
>
<SfInput
v-model='form.city'
label='City'
name='city'
class='form__element form__element--half'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
/>
</ValidationProvider>
<ValidationProvider
name='state'
slim
>
<SfInput
v-model='form.state'
label='State/Province'
name='state'
class='form__element form__element--half form__element--half-even'
/>
</ValidationProvider>
<ValidationProvider
name='country'
rules='required|min:2'
v-slot='{ errors }'
slim
>
<SfSelect
v-model='form.country'
label='Country'
name='country'
class='form__element form__element--half form__select sf-select--underlined'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
>
<SfSelectOption
v-for='countryOption in countries'
:key='countryOption.key'
:value='countryOption.key'
>
{{ countryOption.label }}
</SfSelectOption>
</SfSelect>
</ValidationProvider>
<ValidationProvider
name='zipCode'
rules='required|min:2'
v-slot='{ errors }'
slim
>
<SfInput
v-model='form.postalCode'
label='Zip-code'
name='zipCode'
class='form__element form__element--half form__element--half-even'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
/>
</ValidationProvider>
<ValidationProvider
name='phone'
rules='required|digits:9'
v-slot='{ errors }'
slim
>
<SfInput
v-model='form.phone'
label='Phone number'
name='phone'
class='form__element form__element--half'
required
:valid='!errors[0]'
:errorMessage='errors[0]'
/>
</ValidationProvider>
</div>
<div class='form'>
<div class='form__action'>
<SfButton
v-if='!isFormSubmitted'
:disabled='loading'
class='form__action-button'
type='submit'
>
{{ $t('Select shipping method') }}
</SfButton>
</div>
</div>
<VsfShippingProvider
v-if='isFormSubmitted'
#submit="$router.push('/checkout/billing')"
/>
</form>
</ValidationObserver>
</template>
<script>
import {
SfHeading,
SfInput,
SfButton,
SfSelect
} from '#storefront-ui/vue';
import { ref } from '#vue/composition-api';
import { onSSR, useVSFContext } from '#vue-storefront/core';
import { useShipping, useCountry, useUser } from '#vue-storefront/spree';
import { required, min, digits } from 'vee-validate/dist/rules';
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate';
extend('required', {
...required,
message: 'This field is required'
});
extend('min', {
...min,
message: 'The field should have at least {length} characters'
});
extend('digits', {
...digits,
message: 'Please provide a valid phone number'
});
export default {
name: 'Shipping',
components: {
SfHeading,
SfInput,
SfButton,
SfSelect,
ValidationProvider,
ValidationObserver,
VsfShippingProvider: () => import('~/components/Checkout/VsfShippingProvider')
},
setup() {
const isFormSubmitted = ref(false);
const {
shipping,
load,
save,
loading
} = useShipping();
const {
countries,
load: loadCountries
} = useCountry();
const { isAuthenticated } = useUser();
const { $spree } = useVSFContext();
const form = ref({
email: '',
firstName: '',
lastName: '',
streetName: '',
apartment: '',
city: '',
state: '',
country: '',
postalCode: '',
phone: null
});
const handleFormSubmit = async () => {
if (!isAuthenticated.value) await $spree.api.saveGuestCheckoutEmail(form.value.email);
await save({ shippingDetails: form.value });
isFormSubmitted.value = true;
};
onSSR(async () => {
await load();
await loadCountries();
if (shipping !== undefined) {
form.firstName = shipping.value.firstName;
}
});
return {
shipping,
loading,
isFormSubmitted,
isAuthenticated,
form,
countries,
handleFormSubmit
};
}
};
</script>
onSSR(async () => {
await load();
await loadCountries();
if (shipping.value !== undefined) {
form.firstName = shipping.value.firstName;
}
});
You missed the reactivity by vue, there was a missing .value in the shipping variable.

How to call components with function/method in pages?

My partner create a login page with several components like "email", "password", "phone number", "login button" and "forgot password". But then ask me to move all the components into new vue under '/components' folder. I only know to move the components but I cannot make it functional since the method is not callable. Please help.
original login page:
<template>
<div class="q-pa-md" style="width: 400px">
<q-form
#submit="onSubmit"
#reset="onReset"
class="q-gutter-md"
>
<!-- <q-input
filled
v-model="email"
label="Your email *"
lazy-rules
:rules="[val => !!val || 'Email is missing', isValidEmail]"
/>
<q-input
filled
type="password"
v-model="password"
label="Password *"
hint="Password should be 8 characters"
lazy-rules
:rules="[
val => val !== null && val !== '' || 'Please enter your password',
val => val.length === 8 || 'Please enter a valid password'
]"
/>
<q-input
filled
type="number"
v-model="phone"
label="Your phone number *"
lazy-rules
:rules="[
val => val !== null && val !== '' || 'Please enter your phone number',
val => val.length > 8 && val.length < 12 || 'Please enter a valid number'
]"
/>
<div>
<q-btn label="Login" type="submit" color="primary"/>
<q-btn flat to="/user/requestpassword" label="Forgot Password?" type="submit"/>
</div> -->
</q-form>
</div>
</template>
<script>
export default {
data () {
return {
email: null,
password: null,
phone: null,
accept: false
}
},
methods: {
isValidEmail (val) {
const emailPattern = /^(?=[a-zA-Z0-9#._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}#(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/
return emailPattern.test(val) || 'Invalid email'
},
onSubmit () {
if (this.accept !== true) {
this.$q.notify({
color: 'red-5',
textColor: 'white',
icon: 'warning'
})
} else {
this.$q.notify({
color: 'green-4',
textColor: 'white',
icon: 'cloud_done',
message: 'Submitted'
})
}
this.onReset()
},
onReset () {
this.email = null
this.password = null
this.phone = null
this.accept = false
}
}
}
</script>
new component vue:
<template>
<q-input
filled
v-model="email"
label="Your email *"
lazy-rules
:rules="[val => !!val || 'Email is missing', isValidEmail]"
/>
<q-input
filled
type="password"
v-model="password"
label="Password *"
hint="Password should be 8 characters"
lazy-rules
:rules="[
val => val !== null && val !== '' || 'Please enter your password',
val => val.length === 8 || 'Please enter a valid password'
]"
/>
<q-input
filled
type="number"
v-model="phone"
label="Your phone number *"
lazy-rules
:rules="[
val => val !== null && val !== '' || 'Please enter your phone number',
val => val.length > 8 && val.length < 12 || 'Please enter a valid number'
]"
/>
<div>
<q-btn label="Login" type="submit" color="primary"/>
<q-btn flat to="/user/requestpassword" label="Forgot Password?" type="submit"/>
</div>
</template>
<script>
export default {
}
</script>
<div id="parent">
<child :delegate="delegateMethod" ref="child" />
</div>
<script>
import { ref } from "vue";
export default({
setup() {
const child = ref()
const callChildMethod = () => {
child.method();
};
const delegateMethod = () => {
console.log("call from child");
};
return { delegateMethod };
}
});
</script>
<div id="child">
<p>child component</p>
<button #click="responseToParent()">
</div>
<script>
export default({
props: {
delegate: {
type: Function,
default: () => {}
}
},
setup(props) {
const method = () => {
console.log("call from parent");
};
const responseToParent = () => {
props.delegate();
};
return { responseToParent };
}
});
</script>
This is a very simple example. It works like this between parent and child components in Vue.

VueJS upload image with additional data

I am trying to upload image to the server and the same time to pass some additional data (in the same post request) using: VueJS 2 (CLI 3), axios, multer, sharp and I have NodeJS with MongoDB in the backend.
Front-end:
<form #submit.prevent="onSubmit" enctype="multipart/form-data">
<div class="input">
<label for="name">Name: </label>
<input
type="text"
id="name"
v-model="name">
</div>
<div class="input">
<label for="last_name">Your last_name: </label>
<input
type="text"
id="last_name"
v-model="last_name">
</div>
<div class="input">
<label for="permalink">permalink</label>
<input
type="text"
id="permalink"
v-model="permalink">
</div>
<div class="input">
<label for="price">price</label>
<input
type="text"
id="price"
v-model="price">
</div>
<div class="input">
<label for="photo">photo</label>
<input
style="display: none"
type="file"
id="photo"
#change="onFileSelected"
ref="fileInput">
<div #click="$refs.fileInput.click()">Pick file</div>
</div>
<div class="submit">
<md-button type="submit" class="md-primary md-raised">Submit</md-button>
</div>
</form>
VueJS methods:
import axios from 'axios'
export default {
data () {
return {
name: '',
last_name: '',
permalink: '',
selectedFile: null,
url: null,
price: 0,
second: false
}
},
methods: {
onFileSelected (event) {
this.selectedFile = event.target.files[0]
this.url = URL.createObjectURL(this.selectedFile)
},
onUpload () {
const fd = new FormData()
fd.append('image', this.selectedFile, this.selectedFile.name)
axios.post('http...', fd, {
onUploadProgress: uploadEvent => {
console.log('Upload Progress ' + Math.round(uploadEvent.loaded / uploadEvent.total * 100) + ' %')
}
})
.then(res => {
console.log(res)
})
},
onSubmit () {
const fd = new FormData()
fd.append('image', this.selectedFile, this.selectedFile.name)
fd.append('data', this.name, this.last_name)
axios.post('http://localhost:7000/api/create-user', fd, {
onUploadProgress: uploadEvent => {
console.log('Upload Progress ' + Math.round(uploadEvent.loaded / uploadEvent.total * 100) + ' %')
}
})
.then(res => {
console.log(res)
if (res.data === 'ok') {
this.second = true
}
})
.then(
setTimeout(function () {
this.second = false
this.reset()
}.bind(this), 2000)
)
.catch(error => console.log(error))
}
}
}
NodeJS:
controller.postCreateUser = (req, res) => {
const sharp = require('sharp');
const fs = require('fs');
const folderImg = 'backend/uploads/';
console.log(JSON.stringify(req.body));
console.log(req.file);
res.send("ok");
};
The results of req.file is (which is good):
{ fieldname: 'image',
originalname: 'musk.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: 'backend/uploads/original/',
filename: 'musk-1545470459038.jpg',
path: 'backend\\uploads\\original\\musk-1545470459038.jpg',
size: 108787 }
The results of console.log(req.body) is
{"data":""}
The problem is here data has empty string and I don't receive any data. I need having the data to store to my database. How to do that?
If something isn't very clear to you, ask me for more details.
In your onSubmit method, you do this:
const fd = new FormData()
fd.append('image', this.selectedFile, this.selectedFile.name)
fd.append('data', this.name, this.last_name)
But FormData.append() expects these parameters:
name - The name of the field whose data is contained in value.
value - The field's value. This can be a USVString or Blob (including subclasses such as File).
filename Optional - The filename reported to the server (a USVString), when a Blob or File is passed as the second parameter.
The third parameter does not apply on this line: fd.append('data', this.name, this.last_name)
Instead, you can do either of these:
fd.append('data', `${this.name} ${this.last_name}`) // Send a single String
or
// Send both Strings separately
fd.append('name', this.name)
fd.append('last_name', this.last_name)
or
// Send the data as JSON
fd.append('data', JSON.stringify({name: this.name, last_name: this.last_name}))

vuelidate async validator - how to debounce?

So I have an issue with async validator on my email/user form element. Every time a letter is typed in, it checks for validation. If the email is 30chars, then that is over 30 calls! Anyone know the best way to debounce a vuelidate custom validator? When I try to use debounce, i get all sorts of errors from vuelidate since it's expecting a response.
<div class="form-group">
<label for="emailAddress">Email address</label>
<input type="email" class="form-control col-md-6 col-sm-12" v-bind:class="{ 'is-invalid': $v.user.email.$error }" id="emailAddress" v-model.trim="$v.user.email.$model" #change="delayTouch($v.user.email)" aria-describedby="emailHelp" placeholder="email#example.com">
<small v-if="!$v.user.email.$error" id="emailHelp" class="form-text text-muted">We'll never share your email with anyone.</small>
<div class="error invalid-feedback" v-if="!$v.user.email.required">Email address is required.</div>
<div class="error invalid-feedback" v-if="!$v.user.email.email">This is not a valid email address.</div>
<div class="error invalid-feedback" v-if="!$v.user.email.uniqueEmail">This email already has an account.</div>
</div>
<script>
import { required, sameAs, minLength, maxLength, email } from 'vuelidate/lib/validators'
import webapi from '../services/WebApiService'
import api from '../services/ApiService'
const touchMap = new WeakMap();
const uniqueEmail = async (value) => {
console.log(value);
if (value === '') return true;
return await api.get('/user/checkEmail/'+value)
.then((response) => {
console.log(response.data);
if (response.data.success !== undefined) {
console.log("Email already has an account");
return false;
}
return true;
});
}
export default {
name: "Register",
data() {
return {
errorMsg: '',
showError: false,
user: {
firstName: '',
lastName: '',
email: '',
password: '',
password2: ''
}
}
},
validations: {
user: {
firstName: {
required,
maxLength: maxLength(64)
},
lastName: {
required,
maxLength: maxLength(64)
},
email: {
required,
email,
uniqueEmail //Custom async validator
},
password: {
required,
minLength: minLength(6)
},
password2: {
sameAsPassword: sameAs('password')
}
}
},
methods: {
onSubmit(user) {
console.log(user);
/*deleted*/
},
delayTouch($v) {
console.log($v);
$v.$reset()
if (touchMap.has($v)) {
clearTimeout(touchMap.get($v))
}
touchMap.set($v, setTimeout($v.$touch, 1250))
}
}
}
</script>
When I try to wrap my async function with the debounce, vuelidate does not like it, so I removed it. Not sure how to limit the custom "uniqueEmail" validator.
as 'Teddy Markov' said, you could call $v.yourValidation.$touch() on your input blur event. I think it's more efficient way to use Vuelidate.
<input type="email" class="form-control col-md-6 col-sm-12"
:class="{ 'is-invalid': !$v.user.email.$anyError }"
id="emailAddress" v-model.trim="$v.user.email.$model"
#change="delayTouch($v.user.email)"
#blur="$v.user.email.$touch()"
aria-describedby="emailHelp"
placeholder="email#example.com"
>
I found the best way was to combine with regex for emails
if regex test passes then proceed to check if unique - else skip the check completely.