I am trying to clear a form after I submit it, in this case creating a simple user. I am resetting the state with vuex (see below). But the form stays with data.
this is how the form looks like
<form #submit.prevent="onSubmit" v-if="!loading">
<div class="form-group">
<input placeholder="Name" v-model="user.name" type="text" name="name" class="form-control">
<span class="invalid-feedback" v-if="errors.name">{{ errors.name }}</span>
</div>
<div class="form-group">
<input v-bind:class="{ harError: errors.email }" placeholder="Email" v-model="user.email" type="email" name="email" class="form-control" id="validationCustom03">
<span class="invalid-feedback" v-if="errors.email">{{ errors.email }}</span>
</div>
...
the onSubmit method
/**
* on submitting the form update or crete a user
*/
onSubmit() {
let action = this.id ? 'UPDATE_USER' : 'CREATE_USER';
this.inProgress = true;
this.$store
.dispatch(action)
.then(() => {
console.log('reset or not?');
this.inProgress = false;
// navigate to user
this.$router.push('users');
})
.catch( ({ response }) => {
this.inProgress = false;
this.errors = response.data.errors;
console.log('you have an error on creating an user')
});
},
Resetting the
RESET_STATE({state}) {
console.log('reset state');
for (let f in state) {
Vue.set(state, f, initialState[f]);
}
},
the state like this
const initialState = {
users: [],
user: {
name: '',
email: '',
password: '',
type: '',
bio: '',
photo: '',
active: '1',
},
loading: false,
};
export const store = new Vuex.Store({
namespaced: true,
state: { ...initialState },
...
The input types stais with data
Ok At least I figured out myself, Insead of a const I used a function to set the initialtState like so
function initialState () {
return {
users: [],
user: {
name: '',
email: '',
password: '',
type: '',
bio: '',
photo: '',
active: '1',
},
loading: false,
}
}
export const store = new Vuex.Store({
namespaced: true,
state: { ...initialState() },
then in the mutations i assigned the intitialState to the state
mutations: {
/**
*
* #param state
* #constructor
*/
RESET_STATE: (state) => {
Object.assign(state, initialState())
},
in my user component I dispached it like so
...mapActions(['RESET_STATE']),
...
this.$store.dispatch("RESET_STATE");
Related
I am working on an online shopping project and implying a review system for shopping products. I am using Next.js and MongoDB for my project. It works fine without any reviews submitted but when I submit my review and the error happened as below;
the error is from getServerSideProps in pages/product/[slug].js and the code is as below;
import axios from 'axios';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router'
import React, { useContext, useState, useEffect } from 'react'
import Layout from '../../components/Layout'
import Product from '../../models/Product';
import db from '../../utils/db';
import { Store } from '../../utils/Store';
import { toast } from 'react-toastify';
import Rating from '#material-ui/lab/Rating';
import { getSession } from 'next-auth/react';
import { useForm } from "react-hook-form";
import { getError } from '../../utils/error';
export default function ProductScreen (props) {
const { product, user } = props;
const {state, dispatch} = useContext(Store);
const router = useRouter();
const [ reviews, setReviews ] = useState([]);
const [ rating, setRating ] = useState(0);
const [ comment, setComment ] = useState('');
const [ loading, setLoading ] = useState(false);
const {
handleSubmit,
register,
formState: { errors },
setValue,
} = useForm();
const submitHandler = async () => {
setLoading(true);
try {
await axios.post(`/api/products/${product._id}/reviews`, { rating, comment, user });
setLoading(false);
toast.success('Review submitted successfully');
fetchReviews();
} catch (err) {
setLoading(false);
return toast.error(getError(err));
}
}
const fetchReviews = async () => {
try {
const { data } = await axios.get(`/api/products/${product._id}/reviews`);
setReviews(data);
} catch (err) {
return toast.error('fetchReview err');
}
}
useEffect(() => {
fetchReviews();
}, []);
if (!product) {
return <Layout title="Product Not Found">Product Not Found</Layout>;
}
const addToCartHandler = async () => {
const existItem = state.cart.cartItems.find((x) => x.slug === product.slug);
const quantity = existItem ? existItem.quantity + 1 : 1;
const { data } = await axios.get(`/api/products/${product._id}`);
if (data.countInStock < quantity) {
return toast.error('Sorry. Product is out of stock');
}
dispatch ({ type: 'CART_ADD_ITEM', payload: { ...product, quantity }});
router.push("/cart");
};
return (
<Layout title={product.name}>
<div className='py-2'>
<Link href="/">back to products</Link>
</div>
<div className='grid md:grid-cols-4 md:gap-3'>
<div className='md:col-span-2'>
<Image
src={product.image}
alt={product.name}
width={640}
height={640}
layout='responsive'
/>
</div>
<div>
<ul>
<li className='mt-4'>
<h1 className='text-lg'>{product.name}</h1>
</li>
<li className='mt-4'>Category: {product.category}</li>
<li className='mt-4'>Brand: {product.brand}</li>
<li className='flex mt-4'>
<Rating value={product.rating} readOnly />
<Link href='#reviews'>
<a>({product.numReviews} reviews)</a>
</Link>
</li>
<li className='mt-4'>Description: {product.description}</li>
</ul>
</div>
<div>
<div className='card p-5'>
<div className='mb-2 flex justify-between'>
<div>Price</div>
<div>${product.price}</div>
</div>
<div className='mb-2 flex justify-between'>
<div>Status</div>
<div>{product.countInStock > 0 ? 'In stock' : 'Unavailable'}</div>
</div>
<button className='primary-button w-full' onClick={addToCartHandler}>Add to cart</button>
</div>
</div>
</div>
<div id='reviews' className='mt-4'>
<h2 className='text-xl'>Customer Reviews</h2>
{reviews.length === 0 && <div>No reviews</div>}
{reviews.map((review) => (
<div key={review._id}>
{review.name}
{review.createdAt.substring(0, 10)}
<Rating value={review.rating} readOnly />
{review.comment}
</div>
))}
</div>
<div>
{user ? (
<form onSubmit={handleSubmit(submitHandler)}>
<h1>Leave your review</h1>
<div>
<Rating
name='simple-controlled'
value={rating}
onChange={(e) => setRating(e.target.value)}
/>
<label htmlFor='comment'>Comment</label>
<input
type='text'
{...register('comment', { required: 'Please enter comment',})}
id='comment'
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
{errors.comment && (
<div className='text-red-500'>{errors.comment.message}</div>
)}
<button className='primary-button'>
{loading? 'Loading' : 'Submit'}
</button>
</div>
</form>
) : (
<div>
Please <Link href={`/login?redirect=/product/${product.slug}`}>Login</Link> to write a review.
</div>
)}
</div>
</Layout>
)
}
export async function getServerSideProps(context) {
const { params, req } = context;
const { slug } = params;
const session = await getSession({ req });
await db.connect();
const product = await Product.findOne({ slug }).lean();
await db.disconnect();
return (
{
props: {
product: product ? db.convertDocToObj(product) : null,
user: session ? session : null,
},
}
);
}
as my understanding, the getServerSideProps function at the bottom should convert the props into object with below code;
product: product ? db.convertDocToObj(product) : null,
am I using it correctly?
my product schema with review is as below;
import mongoose from 'mongoose';
const reviewSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
name: { type: String, required: true },
rating: { type: Number, default: 5 },
comment: { type: String, required: true },
},
{
timestamps: true,
});
const productSchema = new mongoose.Schema(
{
name: { type: String, required: true },
slug: { type: String, required: true, unique: true },
category: { type: String, required: true },
image: { type: String, required: true },
price: { type: Number, required: true },
brand: { type: String, required: true },
rating: { type: Number, required: true, default: 0 },
numReviews: { type: Number, required: true, default: 0 },
countInStock: { type: Number, required: true, default: 0 },
description: { type: String, required: true },
reviews: [reviewSchema],
}, {
timestamps: true,
}
);
const Product = mongoose.models.Product || mongoose.model('Product', productSchema);
export default Product;
the MongoDB Compass for Product is as below;
So, how can I fix my problew?
Thanks
Stan
latest Axios version(v1.2.0) changed header field, it is know issue
Not working old code, so you can avoid this error by adding header information.
https://github.com/axios/axios/issues/5298
Your three axios API call needs to add header with correct content type.
(I suppose all of json type in your code)
before
await axios.post(`/api/products/${product._id}/reviews`, { rating, comment, user });
const { data } = await axios.get(`/api/products/${product._id}`);
const { data } = await axios.get(`/api/products/${product._id}/reviews`);
after
await axios.post(`/api/products/${product._id}/reviews`, { rating, comment, user }, {
headers: {
'Content-Type': 'application/json'
}
});
const { data } = await axios.get(`/api/products/${product._id}`,
{
headers: {
'Accept-Encoding': 'application/json'
}
});
const { data } = await axios.get(`/api/products/${product._id}/reviews`,
{
headers: {
'Accept-Encoding': 'application/json'
}
});
I have a Vue component with 5 input elements. As a exercise to learn VueX I wanted to manage the user input in a Vuex store. Let's assume each input represents a line in a poem. My state, mutation and actions look like that
state: {
poem: {
line1: '',
line2: '',
line3: '',
line4: '',
line5: '',
}
},
mutations: {
setPoem(state, line) {
state.poem = {...state.poem, ...line}
},
resetPoem(state) {
state.poem = {
line1: '',
line2: '',
line3: '',
line4: '',
line5: '',
}
}
},
actions: {
setPoem({commit}, line) {
commit('setPoem', line)
},
resetPoem({commit}) {
commit('resetPoem')
},
},
Looking the documentation I found that I could use v-model as usual but with a two-way computed property: https://next.vuex.vuejs.org/guide/forms.html#two-way-computed-property
But it seems not very DRY to create a computed property for each input element like to:
computed: {
line1: {
get() {
return this.$store.state.poem.line1;
},
set(value) {
this.$store.dispatch('setPoem', {line1: value})
}
},
line2: {
get() {
return this.$store.state.poem.line2;
},
set(value) {
this.$store.dispatch('setPoem', {line2: value})
}
},
line3: {
get() {
return this.$store.state.poem.line3;
},
set(value) {
this.$store.dispatch('setPoem', {line3: value})
}
},
line4: {
get() {
return this.$store.state.poem.line4;
},
set(value) {
this.$store.dispatch('setPoem', {line4: value})
}
},
line5: {
get() {
return this.$store.state.poem.line5;
},
set(value) {
this.$store.dispatch('setPoem', {line5: value})
}
}
},
My template looks like this:
<form class="form-group" v-on:submit.prevent="addDocument">
<input v-model="line1" type="text" />
<p class="error">{{errorMsg1}}</p>
<input v-model="line2" type="text" />
<p class="error">{{errorMsg2}}</p>
<input v-model="line3" type="text" />
<p class="error">{{errorMsg3}}</p>
<input v-model="line4" type="text" />
<p class="error">{{errorMsg4}}</p>
<input v-model="line5" type="text" />
<p class="error">{{errorMsg5}}</p>
<button type="submit">Send Poem</button>
</form>
How can I refactor this? Is there a best practice to manage state of multiple forms?
You can use vuex-map-fields
<script>
import { mapFields } from 'vuex-map-fields';
export default {
computed: {
...mapFields([
'poem.line1',
'poem.line2',
'poem.line3',
// ...
]),
},
};
</script>
and in your store, you can import the getField and updateField to fetch and mutate data
...
getters: {
getField,
},
mutations: {
updateField,
}
So I am having a issue trying to submit form data from a b-modal in my Vue component. I can see in my dev tools that the data object is being populated with the data however when I submit my form It doesn't send my data.
here is the form
<b-modal v-model="modalShow" id="myModal">
<form>
<div>
<br>
<input type="text" placeholder="Name" v-model="user.name">
<br>
<input type="text" placeholder="Email" v-model="user.email">
<br>
<input type="text" placeholder="Password" v-model="user.password">
<br>
</div>
<div>
<b-btn #click="modalShow = false">Cancel</b-btn>
<b-btn variant="outline-primary" #click="addUser">Create</b-btn>
</div>
</form>
</b-modal>
Here is the data object and method from the component
data() {
return {
modalShow: false,
user: {
name: '',
email: '',
password: '',
}
}
},
components:{
'b-modal': bModal,
},
directives: {
'b-modal': bModalDirective
},
computed: {
...mapGetters(['users']),
},
methods: {
addUser() {
this.$store.dispatch('addUser', {
name: this.user.name,
email: this.user.email,
password: this.user.password,
})
}
},
and here is the store method being dispatched
addUser(user) {
return new Promise((resolve, reject) => {
axios.post('/register', {
name: user.name,
email: user.email,
password: user.password,
})
.then(response => {
console.log(response)
resolve(response)
})
.catch(error => {
reject(error.response.data)
})
})
},
if I do a return response before processing the data on my back end it shows a empty data object. any ideas why?
The first argument in vuex action is context object, the payload is passed as the second argument
addUser(context, user) {
return new Promise((resolve, reject) => {
axios.post('/register', {
name: user.name,
email: user.email,
password: user.password,
})
.then(response => {
console.log(response)
resolve(response)
})
.catch(error => {
reject(error.response.data)
})
})
}
In my code I have mention preventDefault method on onSubmit event and due to that my page is not getting reload when user enter data in form...
Can you tell me any way by which I can export data to firebase and also reload my page automatically!
Here is the code:
import React, { Component } from 'react';
const firebase = require('firebase');
const uuid = require('uuid');
var config = {
apiKey: "AIzaSyAtpOSiCqFy43ZTE-7CJdcHrIGNN1GrsSk",
authDomain: "electronic-health-record-a795c.firebaseapp.com",
databaseURL: "https://electronic-health-record-a795c.firebaseio.com",
projectId: "electronic-health-record-a795c",
storageBucket: "electronic-health-record-a795c.appspot.com",
messagingSenderId: "545743770560"
};
firebase.initializeApp(config);
class Usurvey extends Component {
constructor(props) {
super(props);
this.state = {
uid: uuid.v1(),
firstName: '',
lastName: '',
};
this.submitData = this.submitData.bind(this);
this.inputData = this.inputData.bind(this);
}
componentDidMount() {
firebase
.database()
.ref(`Newdata/${this.state.uid}`)
.on('value', snap => console.log('from db', snap.val()));
}
submitData(event) {
event.preventDefault();
firebase
.database()
.ref(`Newdata/${this.state.uid}`)
.set({
firstName: this.state.firstName,
lastName: this.state.lastName,
})
.catch(error => console.log(error));
}
inputData(event) {
const firstName = this.refs.name1.value;
const lastName = this.refs.name2.value;
this.setState({ firstName, lastName });
}
render() {
return (
<div>
<form onSubmit={this.submitData}>
<input type="text" onChange={this.inputData} ref="name1" />
<input type="text" onChange={this.inputData} ref="name2" />
<input type="submit" />Submit
</form>
</div>
);
}
}
export default Usurvey;
This is how it should be
class Usurvey extends Component {
constructor(props) {
super(props);
this.state = {
uid: uuid.v1(),
firstName: '',
lastName: '',
};
this.submitData = this.submitData.bind(this);
this.inputData = this.inputData.bind(this);
}
componentDidMount() {
firebase
.database()
.ref(`Newdata/${this.state.uid}`)
.on('value', snap => console.log('from db', snap.val()));
}
submitData(e) {
const { firstName, lastName } = this.state;
e.preventDefault();
firebase
.database()
.ref(`Newdata/${this.state.uid}`)
.set({
firstName: firstName,
lastName: lastName,
})
.catch(error => console.log(error));
this.setState({
firstName: '', lastName: ''
});
}
inputData(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
return (
<div>
<form onSubmit={this.submitData}>
<input type="text" value={this.state.firstName} onChange={this.inputData} name="firstName" />
<input type="text" value={this.state.lastName} onChange={this.inputData} name="lastName" />
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default Usurvey;
You directly update your state as the user inputs in the form, also on your submission you reset your form, so the user can add more information in the form.
If you want to upload your data and reload after it has finished you can do sth like this:
submitData(event) {
event.preventDefault();
firebase
.database()
.ref(`Newdata/${this.state.uid}`)
.set({
firstName: this.state.firstName,
lastName: this.state.lastName,
})
.then(() => {
location.reload();
// or window.location.reload() if location is undefined
})
.catch(error => console.log(error));
}
On submitting the form, I want to show 'Please wait ..' and on successful submission the data returned from server. Using jQuery , it is easy to do. But there should be a React way as React does not like such kind of direct DOM manipulation - I think . 1) Am I right ? 2) How to show the message on (not after ) form submission?
var FormComp = React.createClass({
handleSubmit:function(){
var userName=this.refs.userName.getDOMNode().value.trim();
var userEmail= this.refs.userEmail.getDOMNode().value.trim();
if(!userName || !userEmail){
return;
}
this.props.onFormSubmit({userName:userName, userEmail:userEmail,url:"/api/submit"});
this.refs.userName.getDOMNode().value='';
this.refs.userEmail.getDOMNode().value='';
return;
},
render: function() {
var result=this.props.data;
return (
<div className={result}>{result.message}</div>
<form className="formElem" onSubmit={this.handleSubmit}>
Name: <input type="text" className="userName" name="userName" ref="userName" /><br/>
Email: <input type="text" className="userEmail" name="userEmail" ref="userEmail" /><br/>
<input type="submit" value="Submit" />
<form >
</div>
);
}
});
var RC= React.createClass({
getInitialState: function() {
return {data: ""};
},
onFormSubmit:function(data){
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: data,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render:function(){
return <FormComp onFormSubmit={this.onFormSubmit} data={this.state.data}/>
}
});
React.render(
<RC/>,
document.getElementById('content')
);
This is definitely something React can handle, no direct DOM manipulation is needed. You're almost there, just need to reorganize a little. Here's one way to approach this (with comments around important changes):
var FormComp = React.createClass({
// To get rid of those input refs I'm moving those values
// and the form message into the state
getInitialState: function() {
return {
name: '',
email: '',
message: ''
};
},
handleSubmit: function(e) {
e.preventDefault();
var userName = this.state.name.trim();
var userEmail = this.state.email.trim();
if(!userName || !userEmail) return;
this.setState({
name: '',
email: '',
message: 'Please wait...'
});
// I'm adding a callback to the form submit handler, so you can
// keep all the state changes in the component.
this.props.onFormSubmit({
userName: userName,
userEmail: userEmail,
url: "/api/submit"
}, function(data) {
this.setState({ message: data });
});
},
changeName: function(e) {
this.setState({
name: e.target.value
});
},
changeEmail: function(e) {
this.setState({
email: e.target.value
});
},
render: function() {
// the message and the input values are all component state now
return (
<div>
<div className="result">{ this.state.message }</div>
<form className="formElem" onSubmit={ this.handleSubmit }>
Name: <input type="text" className="userName" name="userName" value={ this.state.name } onChange={ this.changeName } /><br />
Email: <input type="text" className="userEmail" name="userEmail" value={ this.state.email } onChange={ this.changeEmail } /><br />
<input type="submit" value="Submit" />
</form>
</div>
);
}
});
var RC = React.createClass({
onFormSubmit: function(data, callback){
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: data,
success: callback,
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function() {
return <FormComp onFormSubmit={this.onFormSubmit} />
}
});
React.render(
<RC />,
document.getElementById('content')
);