Retrieving data from supabase Postgresql in the form using vue 3 - postgresql

I am trying to fetch the stored data from Profiles table in the postgresql database in supabase, however I seem to fail during the process as the form appear to be blank without any data in the form although the getData() method was called onMounted.Any help would be appreciated. Below are the codes i am using to fetch data from supabase:
<q-dialog v-model="inception">
<q-card class="my-profi">
<q-form>
<q-card-section>
<div class="text-h6">Profile</div>
</q-card-section>
<q-card-section class="q-pt-none">
<div v-if="data">
<q-input :v-model="name" disable>
<template v-slot:prepend>
<q-icon name="person"/>
{{ data.name }}
</template>
</q-input>
<q-input :v-model="reg" disable>
<template v-slot:prepend>
<q-icon name="laptop"/>
{{ data.reg }}
</template>
</q-input>
<q-input :v-model="course" disable>
<template v-slot:prepend>
<q-icon name="book"/>
{{ data.course }}
</template>
</q-input>
<q-input :v-model="phone" disable>
<template v-slot:prepend>
<q-icon name="call"/>
{{ data.phone }}
</template>
</q-input>
</div>
</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-btn flat label="Edit Profile" #click="profile = true" />
<q-btn color="red" flat label="Close" v-close-popup />
</q-card-actions>
</q-form>
</q-card>
</q-dialog>
</q-card-section>
</q-card>
</q-page>
</q-page-container>
</q-layout>
</div>
</template>
<script>
import { ref,computed,onMounted,toRefs,defineProps } from 'vue'
import lb from '../assets/download.jpeg'
import bg from '../assets/logo.svg'
import { useRouter } from 'vue-router'
import store from '#/store/index'
import supabase from '#/supabase'
export default {
setup () {
const user = computed(()=> store.state.user)
const props = defineProps(['session'])
const {session} = toRefs(props)
const name = ref('')
const reg = ref('')
const phone = ref('')
const course = ref('')
const picture = ref('')
const statusMsg = ref(null)
const router = useRouter()
// const route = useRouter()
// const currentId = route.params.dashId
onMounted(()=>{
getData()
})
const getData = async() =>{
try{
const {user} = session.value
const{ data, error} = await supabase.from("profiles").select('name','reg','phone','course').eq('id', user.id).single()
if (error) throw error
if(data){
name.value = data.name
reg.value = data.reg
phone.value = data.phone
course.value = data.course
}
}catch(error){
error.value = error.message
setTimeout(() => {
error.value = false
}, 5000);
}
}
const addTodo= async()=>{
try{
const {error}= await supabase.from("Profiles").insert([
{
name:name.value,
reg:reg.value,
phone:phone.value,
course:course.value,}
])
if(error) throw error
statusMsg.value = "Saved successfully"
name.value = null,
reg.value =null,
phone.value= null,
course.value = null,
setTimeout(() => {
statusMsg.value = false
}, 5000);
}
catch (error){
error.value = `Error ${error.message}`
setTimeout(() => {
error.message = false
}, 5000);
}
}
const handleClick= async()=>{
await supabase.auth.signOut()
router.push('/')
}
return {
drawer: ref(false),
miniState: ref(true),
bg:bg,
lb:lb,
name,
reg,
phone,
course,
picture,
inception: ref(false),
secondDialog: ref(false),
profile: ref(false),
handleClick,
user,
addTodo,
}
}
}

Related

values.map is not a function Sequelize

I'm trying to send a form to my db but I get this error :
The problem XD
I have this problem only when I try to do it from the front, because when I try with Insomnia it works. So I'm not sure where the problem is coming from.
I'm using multer for the image.
The model:
const { DataTypes } = require('sequelize');
// Exportamos una funcion que define el modelo
// Luego le injectamos la conexion a sequelize.
module.exports = (sequelize) => {
// defino el modelo
sequelize.define('Recipe', {
id:{
type: DataTypes.UUID(5),
primaryKey:true,
defaultValue:DataTypes.UUIDV4(5)
},
title: {
type: DataTypes.STRING,
allowNull: false,
},
summary:{
type:DataTypes.STRING,
allowNull: false,
},
healthScore:{
type: DataTypes.INTEGER,
},
steps:{
type:DataTypes.ARRAY(DataTypes.STRING)
},
dishTypes:{
type:DataTypes.ARRAY(DataTypes.STRING)
},
readyInMinutes:{
type: DataTypes.INTEGER,
get(){
return "Ready in " + this.getDataValue("readyInMinutes") + " minutes"
}
},
ingredients:{
type: DataTypes.ARRAY(DataTypes.STRING)
},
servings:{
type:DataTypes.STRING
},
image:{
type:DataTypes.STRING,
defaultValue: `https://post.healthline.com/wp-content/uploads/2020/08/food-still-life-salmon-keto-healthy-eating-732x549-thumbnail-732x549.jpg`
}
},{
timestamps: false
});
};
The post route (actually the helper):
const createNewRecipe = require("../helpers/RecipeCont/CreateRecipe/CreateRecipe")
const createNewRecipeRoute = async (req, res) => {
try {
const data = {
title,
summary,
healthScore,
steps,
dishTypes,
readyInMinutes,
ingredients,
servings,
Diet_type,
} = req.body;
const image = req.file.path
let newRecipe = await createNewRecipe({
title,
summary,
healthScore,
steps,
dishTypes,
readyInMinutes,
ingredients,
servings,
image,
});
await newRecipe.addDiet_type(Diet_type)
console.log(req.file)
res.status(200).json("Receta creada con éxito");
} catch (error) {
console.log(error)
res.status(400).json(error.message);
}
}
module.exports = createNewRecipeRoute;
The form
import React, {useState} from "react";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { createRecipe, getDietTypes } from "../../actions/actions";
import styles from "./form.module.css"
export default function Form(){
const [form,setForm] = useState({
title:"",
summary:"",
healthScore:0,
steps:[],
dishTypes:[],
readyInMinutes:0,
ingredients:[],
servings:0,
image:"",
Diet_type:[1]
})
const [steps, setSteps] = useState("")
const [dishTypes, setDishType]=useState("")
const [ingredients, setIngredients]= useState("")
const dispatch=useDispatch()
useEffect(()=>{
dispatch(getDietTypes())
},[])
const diets = useSelector(state=> state.diet)
const changeHandler=(e)=>{
if(e.target.name==="image"){
setForm({...form,[e.target.name]: e.target.file })
}
setForm({...form, [e.target.name]:e.target.value})
}
const stepHandler = (e)=>{
let aux = e.target.name
let auxV = e.target.value
if(e.key==="Enter"){
e.preventDefault()
setForm({...form, [e.target.name]: [...form[aux] , auxV ]})
aux==="steps"? setSteps("") : aux==="ingredients"? setIngredients("") : setDishType("")
}
console.log(form)
}
const deleteHandler = (e)=>{
let help = e.target.name
e.preventDefault()
let aux = form[help].filter(s=> s!==e.target.value)
setForm({...form, [help]: [...aux]})
}
const imageHandler = (e)=>{
setForm({...form, [e.target.name]:e.target.files[0]})
}
const sendHandler = (e)=>{
e.preventDefault();
if(form.image!==""){
const formData = new FormData()
formData.append("image",form.image)
formData.append("title",form.title)
formData.append("summary",form.summary)
formData.append("healthScore",form.healthScore)
formData.append("steps",form.steps)
formData.append("dishTypes",form.dishTypes)
formData.append("readyInMinutes",form.readyInMinutes)
formData.append("Ingredients",form.ingredients)
formData.append("servings",form.servings)
formData.append("Diet_type",form.Diet_type)
for (var key of formData.entries()) {
console.log(key[0] + ', ' + key[1]);
}
dispatch(createRecipe(formData))
} else {
dispatch(createRecipe(form))
}
console.log(form)
}
return(
<>
<div className={styles.div} >
<h2>Create your own recipe!</h2>
<form encType="multipart/form-data" method="POST" onSubmit={sendHandler}>
<div className={styles.divTitle}>
<h2>Title:</h2>
<input type="text" placeholder="Title" name="title" value={form.title} onChange={changeHandler}></input>
</div>
<input type="text" placeholder="summary" name="summary" value={form.summary} onChange={changeHandler}></input>
{form.healthScore}<input type="range" placeholder="healthScore" name="healthScore" min="1" max="100" step="1" value={form.healthScore} onChange={changeHandler} ></input>
<div>
<input type="text" name="steps" value={steps} onChange={(e)=>{setSteps(e.target.value)}} onKeyDown={stepHandler} placeholder="Steps"></input>
<div>
{form.steps.length>0? form.steps.map(e=><li>{e}<button value={e} name="steps" onClick={deleteHandler}>x</button></li>) : "Add the steps of your recipe!"}
</div>
</div>
<div>
<input type="text" name="ingredients" value={ingredients} onChange={(e)=>{setIngredients(e.target.value)}} onKeyDown={stepHandler} placeholder="Ingredients"></input>
<div>
{form.ingredients.length>0? form.ingredients.map(e=><li>{e}<button value={e} name="ingredients" onClick={deleteHandler}>x</button></li>) : "Add the ingredients of your recipe!"}
</div>
</div>
<div>
<input type="text" name="dishTypes" value={dishTypes} onChange={(e)=>{setDishType(e.target.value)}} onKeyDown={stepHandler} placeholder="Dish types"></input>
<div>
{form.dishTypes.length>0? form.dishTypes.map(e=><li>{e}<button value={e} onClick={deleteHandler}>x</button></li>) : "Add the dish types of your recipe!"}
</div>
</div>
<select>
{console.log(diets)}
{diets?.map(d=><option key={d.id} id={d.id}>{d.name}</option>)}
</select>
<input className={styles.number} name="readyInMinutes" value={form.readyInMinutes} onChange={changeHandler} type="number"></input>
<input className={styles.number} name="servings" value={form.servings} onChange={changeHandler} type="number"></input>
<input type="file" name="image" onChange={imageHandler}></input>
<input type="submit"></input>
</form>
</div>
</>
)
}
I'm still working on the form, in Diet_type for example, but even trying to hardcode the state to make the post it doesn't work.
The "for" is because the console.log doesn't work with formData and at the beginning I thought it was that I wasn't sending anything, but actually I do.
I save the image of all the request even for those which can fulfill so the middleware seems its working too.
I hope you can help me to understand what's going on and try to find a solution, c: Thanks for your time!!
The object values does not have the map methode.
You expect that there should be a map methode, but this is not a javascipt array with protype map. So you get the error.
You can use the loadash library.
const {map} require(`lodash`)
map(values, value => {
console.log(value)
// your code coes here
}

Cannot access 'getSession' before initialization: How to resolve circular dependencies in SWR/Passport.js?

I'm trying to implement MongoDB, Next.JS and Passport.js, However upon starting my app and while trying to login, I get this 500 status error:
Cannot access 'getSession' before initialization
The preamble for all this is I am using a hook which calls/api/user to see if the req.user has been set by passport thus creating the 'user' below. And if one exists you get access to the other links/routes in the app.
This is the function declaration regarding the getSession function in the session.js file. So I suspect this is what is causing the circular dependencies issue, naturally.
import MongoStore from "connect-mongo";
import nextSession from 'next-session';
import { getMongoClient } from "./mongodb";
const mongoStore = MongoStore.create({
clientPromise: getMongoClient(),
stringify: false,
});
const getSession = nextSession({
store: mongoStore,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 2 * 7 * 24 * 60 * 60, // 2 weeks,
path: "/",
sameSite: "strict",
},
touchAfter: 1 * 7 * 24 * 60 * 60, // 1 week
});
export default function session(req, res, next) {
getSession(req, res);
next();
}
And this is the auth file with the passport file, passport initialization and session.
import passport from '../lib/passport'
import session from '../lib/session'
const auths = [session, passport.initialize(), passport.session()];
export default auths;
/component/Layout
export default function Layout({ children, showFooter = false }) {
const [user, { mutate }] = useCurrentUser();
async function handleLogout() {
axios
.get('/api/logout').then(() => {
mutate({ user: null })
Router.push('/',)
}).catch(err => console.log('err', err))
}
return <>
<div className="shadow bg-base-200 drawer h-screen">
<div className="flex-none hidden lg:block">
<ul className="menu horizontal">
{user
?
<>
<li>
<Link href="/profile">
<a>Profile</a>
</Link>
</li>
<li>
<Link href="/dashboard">
<a>Dashboard</a>
</Link>
</li>
<li>
<a role="button" onClick={handleLogout}>
Logout
</a>
</li>
<li className="avatar">
<div className="rounded-full w-10 h-10 m-1">
<img src="https://i.pravatar.cc/500?img=32" />
</div>
</li></>
:
<>
<li>
<Link href="/login">
<a>Login</a>
</Link>
</li>
<li>
<Link href="/registration">
<a>Register</a>
</Link>
</li>
</>
}
</ul>
</div>
</div>
</>;
}
This is the hook itself:
import useSWR from 'swr';
export const fetcher = (url) => {
try {
return fetch(url).then((res) => {
console.log('res', res)
return res.json()
})
} catch (error) {
console.log('error', error)
}
}
export function useCurrentUser() {
const { data, mutate } = useSWR('/api/user', fetcher);
const user = data?.user;
return [user, { mutate }];
}
export function useUser(id) {
const { data } = useSWR(`/api/users/${id}`, fetcher, {
revalidateOnFocus: false,
});
return data?.user;
}
And this is the api: /api/user:
import nextConnect from 'next-connect'
import auth from '/middleware/auth'
const handler = nextConnect()
handler
.use(auth)
.get((req, res) => {
console.log("req.user ", req.user);
if (req.user) {
res.json({ user: req.user })
} else {
res.json({ user: null })
}
})
export default handler
Also this is my repo
And this is a link to the app on vercel.

MERN stack binary image is just a black square

I'm building a full-stack app with a MERN stack (mongo db, Express, React and Node.js)
I'm currently able to cave my image to the mongo db as a binary file.
The the mongo shell database output is
"img" : BinData(0,"QzpcZmFrZXBhdGhcc2FtcGxlLnBuZw=="),
so I would assume that the upload is fine.
When it comes to converting it back to an image on the front end I am using
src={`data:image/png;`+btoa(`${Buffer.from(img.data).toString('base64')}`)}
which gives me a string that looks like this...
data:image/png;UXpwY1ptRnJaWEJoZEdoY2MyRnRjR3hsTG5CdVp3PT0=
However, on the front-end it shows a broken image icon and when I go to this link it just shows
a teeny tiny black square.
I have tried a lot of combinations of concatenating the string but can't seem to get it to display.
Thanks in advance !
Ok, so after many differnet strategies I managed to combine a few bit of code here and there plus some improvisation to build this...
ImageUpload.js
import React from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
class ImageUpload extends React.Component {
constructor(props) {
super(props);
this.state = {
image: {
name: "",
img: ""
},
submitted: false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
const { name, value } = event.target;
const { image } = this.state;
this.setState({
image: {
...image,
[name]: value,
}
});
}
onSelectChange = (e) => {
const values = [...e.target.selectedOptions].map(opt => opt.value);
this.props.onChange(values);
};
handleSubmit(event) {
event.preventDefault();
console.log(this.state);
this.setState({ submitted: true });
const { image } = this.state;
if (image.name) {
this.props.register(image);
}
}
_onChange = (e) => {
const file = this.refs.uploadImg.files[0]
const reader = new FileReader();
reader.onloadend = () => {
this.setState({
image: {
img: reader.result
}
})
}
if (file) {
reader.readAsDataURL(file);
this.setState({
image: {
img: reader.result
}
})
}
else {
this.setState({
image: {
img: ""
}
})
}
}
render() {
const { registering } = this.props;
const { image, submitted } = this.state;
console.log(this.handleChange);
render() {
const { registering } = this.props;
const { image, submitted } = this.state;
console.log(this.handleChange);
return (
<div className="col-md-9 col-md-offset-3">
<form name="form" onSubmit={this.handleSubmit}>
<div className={'form-group' + (submitted && !image.img ? ' has-error' : '')}>
<label htmlFor="img">Image</label>
<input
ref="uploadImg"
type="file"
name="selectedFile"
onChange={this._onChange}>
{/* value={toString(window.form.elements[0].nextElementSibling.src)}> */}
</input>
<img src={this.state.image.img} />
{submitted && !image.img &&
<div className="help-block">Image is required</div>
}
</div>
<div className="form-group">
<button className="btn btn-primary">Register Building</button>
{registering && }
<Link to="/home" className="btn btn-link">Cancel</Link>
</div>
</form>
</div>
The mongoose Schema is just a simple string:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
Apartment = require('../apartments/apartment.model')
const schema = new Schema(
name: { type: String },
img: { type: String }
}
);
schema.set('toJSON', { virtuals: true });
module.exports = mongoose.model('image', schema);
Hope it helps anyone else. Also if anyone wouldn't mind explaining the potential flaws in uploading extrememly looong strings for images (and any solutions to this) that'd be a big help too. Would there be a way to enter this as a Buffer base64 and convert it? would the db read it this way ?
Thanks !

Function Query.where() requires a valid third argument, but it was undefined when trying to view the page

I'm trying to implement rating system in my shopping app, but received error console when trying to open the page.
The error on console are:
Function Query.where() requires a valid third argument, but it was undefined. - it points to:
this.stars = this.starService.getProductStars(this.movieId)
AND
const starsRef = this.afs.collection('stars', ref => ref.where('movieId', '==', movieId));
Below is my code:
rating.page.html: (where I put my 2 of components which are TestRate and Star-Review)
<ion-content>
<app-testrate></app-testrate>
<app-star-review-component></app-star-review-component>
</ion-content>
testrate.component.html:
<div *ngIf="movie | async as m">
<h1>
{{m.title}}
</h1>
<img [src]="m.image" width="100px">
<p>
{{m.plot}}
</p>
<star-review [movieId]="movieId" [userId]="userId"></star-review>
</div>
testrate.component.ts:
export class TestrateComponent implements OnInit {
userDoc: AngularFirestoreDocument<any>;
movieDoc: AngularFirestoreDocument<any>;
user: Observable<any>;
movie: Observable<any>;
constructor(private afs: AngularFirestore) { }
ngOnInit() {
this.userDoc = this.afs.doc('users/test-user-3')
this.movieDoc = this.afs.doc('movies/battlefield-earth')
this.movie = this.movieDoc.valueChanges()
this.user = this.userDoc.valueChanges()
}
get movieId() {
return this.movieDoc.ref.id
}
get userId() {
return this.userDoc.ref.id
}
}
star-review.component.html:
<h3>Average Rating</h3>
{{ avgRating | async }}
<h3>Reviews</h3>
<div *ngFor="let star of stars | async">
{{ star.userId }} gave {{ star.movieId }} {{ star.value }} stars
</div>
<h3>Post your Review</h3>
<fieldset class="rating">
<ng-container *ngFor="let num of [5, 4, 3, 2, 1]">
full star
<input (click)="starHandler(num)"
[id]="'star'+num"
[value]="num-0.5"
name="rating"
type="radio" />
<label class="full" [for]="'star'+num"></label>
half star
<input (click)="starHandler(num-0.5)"
[value]="num-0.5"
[id]="'halfstar'+num"
name="rating"
type="radio" />
<label class="half" [for]="'halfstar'+num"></label>
</ng-container>
</fieldset>
star-review.component.ts:
export class StarReviewComponentComponent implements OnInit {
#Input() userId;
#Input() movieId;
stars: Observable<any>;
avgRating: Observable<any>;
constructor(private starService: StarService) { }
ngOnInit() {
this.stars = this.starService.getProductStars(this.movieId)
this.avgRating = this.stars.pipe(map(arr => {
const ratings = arr.map(v => v.value)
return ratings.length ? ratings.reduce((total, val) => total + val) / arr.length : 'not reviewed'
}))
}
starHandler(value) {
this.starService.setStar(this.userId, this.movieId, value)
}
}
star.service.ts:
export class StarService {
constructor(private afs: AngularFirestore) { }
// Star reviews that belong to a user
getUserStars(userId) {
const starsRef = this.afs.collection('stars', ref => ref.where('userId', '==', userId));
return starsRef.valueChanges();
}
// Get all stars that belog to a Product
getProductStars(movieId) {
const starsRef = this.afs.collection('stars', ref => ref.where('movieId', '==', movieId));
return starsRef.valueChanges();
}
// Create or update star
setStar(userId, movieId, value) {
// Star document data
const star: Star = { userId, movieId, value };
// Custom doc ID for relationship
const starPath = `stars/${star.userId}_${star.movieId}`;
// Set the data, return the promise
return this.afs.doc(starPath).set(star)
}
}

How to prevent repeating code of form validation

I created the form for multiple inputs, where the specific input data shall be validated at the time of data entry and once again for all data just before the submission of the form to the backend.
The conditions to submit: all fields are mandatory and the data is valid.
My program works, but I don't like that I'm repeating the validation code in 2 places: in ErrorOutput and hadleSubmit.
In ErrorOutput I check the data and, if necessary, display an error message.
In handleSubmit I just check the data without displaying of error message and if all data is valid, I confirm submitting.
How can I improve my example to prevent the repetition of this code, but the data validation was also at the time of data entry and before submission?
import React from 'react'
import { render } from 'react-dom'
const ErrorOutput = props => {
let name = props.name
let inputValue = props.case
let submit = props.submit
// Data validation
if (name === 'firstName') {
if (!inputValue.match(/^[a-zA-Z]+$/) && inputValue.length > 0) {
return <span>Letters only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
if (name === 'telNo') {
if(!inputValue.match(/^[0-9]+$/) && inputValue.length > 0) {
return <span>Numbers only</span>
} else if (submit && inputValue.length === 0) {
return <span>Required</span>
}
return <span></span>
}
}
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false
}
}
handleSubmit(e){
e.preventDefault()
let submit = true
let error = true
const { firstName, telNo } = this.state
this.setState ({submit: submit})
// Repeat the data validation before submission
if (firstName === '' || !firstName.match(/^[a-zA-Z]+$/)) {
error = true
} else if (telNo === '' || !telNo.match(/^[0-9]+$/)) {
error = true
} else {
error = false
}
// Submited if all data is valid
if (!error) {
// send data
return alert('Success!')
}
}
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>
First name:
</label>
<input
type='text'
name ='firstName'
value = {this.state.firstName}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.firstName} name={'firstName'} submit = {this.state.submit} />
</div>
<div>
<label>
Phone number:
</label>
<input
type='tel'
name ='telNo'
value = {this.state.telNo}
onChange = {this.handleValidation.bind(this)}
/>
<ErrorOutput case={this.state.telNo} name={'telNo'} submit = {this.state.submit} />
</div>
<button>
Submit
</button>
</form>
)
}
}
render(
<App />,
document.getElementById('root')
)
You could extract a FormItem component:
class FormItem extends React.Component {
render() {
return (
<div>
<label>
{this.props.label}
</label>
<input
{...this.props.input}
/>
<ErrorOutput
case={this.props.input.value}
name={this.props.input.name}
submit={this.props.onSubmit}
/>
</div>
);
}
}
and use it in your App:
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<FormItem label='First name:' input={{
type: 'text'
name: 'firstName'
value: this.state.firstName,
onChange: this.handleValidation.bind(this)
}}
onSubmit={this.state.submit}
/>
<FormItem label='Phone number:' input={{
type:'tel'
name :'telNo'
value : {this.state.telNo}
onChange : {this.handleValidation.bind(this)}
}}
onSubmit={this.state.submit}
/>
<button>
Submit
</button>
</form>
)
}
this is where libraries like react-final-form and redux-form become handy.
UPD
ErrorOutput component should not validate anything, it is not a responsibility of a component. Instead, you could validate your values on inputs blur event and before submit:
class App extends React.Component {
constructor(props){
super(props)
this.state = {
firstName: '',
telNo: '',
submit: false,
errors: {},
invalid: false,
}
}
handleSubmit(e){
e.preventDefault()
if (this.validate()) {
// handle error
} else {
// submit
}
}
validate = () => {
const { firstName, telNo } = this.state
const errors = {}
let invalid = false;
if (firstName === '' || !firstName.match(/^[a-zA-Z]+$/)) {
errors.firstName = 'first name is required'
invalid = true;
} else if (telNo === '' || !telNo.match(/^[0-9]+$/)) {
telNo.telNo = 'telNo is required'
invalid = true;
}
this.setState({
invalid,
errors,
})
return invalid;
}
render() {
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<FormItem label='First name:' input={{
type: 'text',
name: 'firstName',
value: this.state.firstName,
onChange: e => this.setState({ firstName: e.target.value }),
onBlur: () => this.validate(),
}}
/>
<FormItem label='Phone number:' input={{
type: 'tel',
name: 'telNo',
value: this.state.telNo,
onChange: e => this.setState({ telNo: e.target.value }),
onBlur: () => this.validate(),
}}
/>
<button>
Submit
</button>
</form>
)
}
}
and FormItem and ErrorOutput:
const ErrorOutput = ({ error }) => <span>{error}</span>
class FormItem extends React.Component {
render() {
return (
<div>
<label>
{this.props.label}
</label>
<input
{...this.props.input}
/>
{this.props.error && <ErrorOutput error={this.props.error} />}
</div>
);
}
}
const ErrorOutput = ({ errorText }) => <span>{errorText}</span>;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
firstName: "",
telNo: "",
submit: false,
errors: {} //Add errors object to the state.
};
}
handleSubmit(e) {
e.preventDefault();
const errors = this.validateData();
if (Object.keys(errors).length === 0) {
alert("Success");
}
//else errors exist
this.setState({ errors });
}
validateData = () => {
let errors = {};
const { firstName, telNo } = this.state; // read the values to validate
if (firstName.length === 0) {
errors.firstName = "Required";
} else if (firstName.length > 0 && !firstName.match(/^[a-zA-Z]+$/)) {
errors.firstName = "Letters only";
}
if (telNo.length === 0) {
errors.telNo = "Required";
} else if (telNo.length > 0 && !telNo.match(/^[0-9]+$/)) {
errors.telNo = "Numbers only";
}
return errors;
};
handleValidation(e) {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
const { errors } = this.state; // read errors from the state
return (
<form onSubmit={this.handleSubmit.bind(this)}>
<div>
<label>First name:</label>
<input
type="text"
name="firstName"
value={this.state.firstName}
onChange={this.handleValidation.bind(this)}
/>
{errors.firstName && <ErrorOutput errorText={errors.firstName} />}
</div>
<div>
<label>Phone number:</label>
<input
type="tel"
name="telNo"
value={this.state.telNo}
onChange={this.handleValidation.bind(this)}
/>
{errors.telNo && <ErrorOutput errorText={errors.telNo} />}
</div>
<button>Submit</button>
</form>
);
}
}
render(<App />, document.getElementById("root"));