I am trying to write a react code to submit the value to the backend server.
I want the input field to be cleared out as soon as the user hits submit button.
I have written the below code, could anyone help me with what I am missing here?
class Create extends Component {
state = {
task : {
title: '',
completed: false
}
}
CreateHandler = (event) => {
this.setState((state) => {
return {
task: {
...state, title: '' // <----- CLEARING HERE (well, trying)
}
}
});
event.target.value=""; // <----- ALSO HERE
event.preventDefault();
axios({
method:'post',
url:'http://localhost:8000/api/task-create',
data: this.state.task,
xsrfHeaderName: this.props.CSRFToken
})
.then((res) => {
console.log(res.data);
})
this.props.updateState(this.state.task)
}
ChangeHandler = (event) => {
this.setState(state => {
return {
task: {
...state, title: event.target.value
}
}
})
}
Breaking the code in parts so that it's easily readable.
render() {
return (
<form onSubmit={this.CreateHandler.bind(this)}>
<div className="header form-group">
<input
className="newItem form-control"
onChange={this.ChangeHandler.bind(this)}
value={this.state.task.title}
/>
<button
type="submit"
class="saveButton btn btn-primary btn-warning">
submit
</button>
</div>
</form>
)
}
}
export default Create;
The end goal is to clear the input field and then send the data to the backend django server, which is being done successfully except the input field being cleared.
You are not updating state correctly
this.setState((state) => {
return {
task: {
...state, title: '' // <----- CLEARING HERE (well, trying)
}
}
});
should be
this.setState((state) =>({...state, task: {...state.task, title: ''}}))
In your case, it could be done like this:
this.setState(previousState => ({
task: {
...previousState.task,
title: '' // <----- CLEARING HERE
}
}));
A better way to write your createHandler method:
CreateHandler = (event) => {
// Prevent the default form action
event.preventDefault();
// Call your API
axios({
method: "post",
url: "http://localhost:8000/api/task-create",
data: this.state.task,
xsrfHeaderName: this.props.CSRFToken,
}).then((res) => {
// Request passed
// Call your prop function
this.props.updateState(this.state.task);
// Clear the unnecessary data
this.setState((prevState) => ({
// Create new object
task: {
// Assign the properties of previous task object
...prevState.task,
// Clear the title field
title: "",
},
}));
});
};
Hope this helps!
Related
I just need some help identifying what I am missing here. Just can't seem to send the correct data through:
Parent with the CommunicationPreference component:
<CommunicationPreference
v-for="(communication, index) in communicationPreference"
:key="index"
:consent="communication.consent"
:name="communication.name"
#update="updateConsent(consent)"
/>
METHOD
methods: {
async updateConsent(consent) {
await this.$store.dispatch('account/updateCommunicationPreferences', { consent })
},
},
CommunicationPrefernce.vue
<Button
class="mr-4"
:text="YES"
:type="consent === true ? 'primary' : 'secondary'"
#clicked="updateConsent(true)"
/>
<Button
:text="NO"
:type="consent !== true ? 'primary' : 'secondary'"
#clicked="updateConsent(false)"
/>
PROPS:
props: {
type: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
consent: {
type: Boolean,
default: true,
},
},
METHOD:
updateConsent(consent) {
this.$emit('update', consent)
},
STORE:
async updateCommunicationPreferences({ commit, state }, payload) {
const { consent } = payload
const { communicationTypeName } = state.communicationTypeName
try {
const response = await this.$axios.put(`/communication-consent/${communicationTypeName}`, consent)
const { data: updatedCommunicationPreferences } = response.data
commit('SET_UPDATED_COMMUNICATION_PREFERENCES', updatedCommunicationPreferences)
} catch (error) {
commit('ADD_ERROR', { id: 'updateCommunicationPreferences', error }, { root: true })
}
},
Attached is the UI I am working towards for reference. the idea is each time the user selects either YES or NO the selection is updated and reflected on the UI
Here is my Swagger doc:
I assume that you have a mapped getter for communicationPreference prop, so that this is correct.
I also assume that your #clicked event prop is proper provided the implementation of Button.vue.
So try to change #update="updateConsent(consent)" to #update="updateConsent"
Right now it seems to me that you are making a small mistake between a function call and declaration. Having it such as #update="updateConsent" will trigger updateConsent method, and the function declaration:
async updateConsent(consent) {
await this.$store.dispatch('account/updateCommunicationPreferences', { consent })
},
will take care of getting the consent you pass in your event trigger.
Im using vuex and I have an action
storeExpense(context, params){
axios.post('api/expenses', params)
.then( response => {
console.log("Expense Created");
})
.catch( error => {
console.log(error);
});
}
and on my Expense.vue im using the action via
this.$store.dispatch('storeExpense',this.expense)
.then( response => {
this.modalShow = false
this.$swal(
'Success',
'Expense has been created!',
'success'
)
})
I dont have an error but after the expense was created the state is not updating therefore I need to refresh the page in order for my table to get the latest data.
I have a mutation called
mutateExpenses(state, payload){
state.expenses = payload
}
however when i use this after the response it overrides the whole state.expenses object to a single object because this.expense is a single object
Im new to vuex.
You must update your store using mutations that are called inside your actions.
I suggest you to dive a bit into the Vuex documentation, especially the mutations and actions :)
Here is an example of how to use the store :
It goes dispatch --> action --> mutation
// Your store
const store = new Vuex.Store({
state: {
posts: [],
isLoading: false
},
mutations: {
// Must be called by actions AND ONLY by actions
add(state, post) {
// Add the given post to the 'posts' array in our state
Vue.set(state.posts, state.posts.length, post)
},
busy(state) {
Vue.set(state, 'isLoading', true)
},
free(state) {
Vue.set(state, 'isLoading', false)
}
},
actions: {
create({
commit
}, post) {
commit('busy')
axios.post('https://jsonplaceholder.typicode.com/posts', post)
.then(response => {
// Call the mutation method 'add' to add the newly created post
commit('add', response.data)
})
.catch((reason) => {
// Handle errors
})
.finally(() => {
commit('free')
});
},
}
})
// Your Vue app
new Vue({
el: "#app",
store,
data: {
post: {
title: 'foo',
body: 'bar',
userId: 1
}
},
methods: {
onButtonClicked() {
this.$store.dispatch('create', this.post)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<button #click="onButtonClicked">Create Post</button>
<div>Posts : <span v-if="$store.state.isLoading">Requesting</span></div>
<div v-for="post in $store.state.posts">
{{post}}
</div>
</div>
I have got a Vue Component which has a list of values, when you select these values this changed the selected array, which in tern is posted to an endpoint.
I have an issue if the user spam clicks these values, as an individual post is created for each change, I want it so that if the user selects another item then the currently pending post is cancelled, so then the new value is posted and updates the endpoint with both the selected items.
However i'm having an issue with aborting the current axios request, I have provided the code below. There are no errors, the request simply doesn't cancel.
export default {
props: {
endpoint: {
default: '',
type: String
},
parameters: {
default: null,
type: Object
}
},
data: () => ({
loaded: false,
selected: [],
save: [],
data: [],
cancel: undefined
}),
methods: {
update() {
const self = this;
let params = this.parameters;
params.data = this.selected;
this.$root.$emit('saving', {
id: this._uid,
saving: true
});
if (self.cancel !== undefined) {
console.log('cancel');
this.cancel();
}
window.axios.post(this.endpoint + '/save', params, {
cancelToken: new window.axios.CancelToken(function executor(c) {
self.cancel = c;
})
}).then(() => {
this.$nextTick(() => {
this.loaded = true;
this.$root.$emit('saving', {
id: this._uid,
saving: false
});
});
}).catch(function (thrown) {
if (window.axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
}
});
}
}
}
I have got a global instance of Axios created on my Vue Application.
I have the weirdest bug I have ever encountered. I am using Axios and Vee-Validate in my Vue project and from my api I get an error. So withing axios I have a catch.
example:
this.$http.post('v1/auth/register', {
first_name: this.first_name,
last_name: this.last_name,
email: this.email,
phone: this.phone,
password:this.password
}).then((response) => {
this.registration_card = 2;
}).catch((error) => {
if(error.data.error.message === "email_already_exists") {
let input = this.$refs['email'].$children[0];
input.errors.add({ field: 'email', msg: 'email already is use'});
this.loading = false;
console.log(input.errors);
console.log(this.loading);
}
});
Now here comes the weird part. With this code:
let input = this.$refs['email'].$children[0];
input.errors.add({ field: 'email', msg: 'email already is use'});
this.loading = false;
the input.errors is still empty and error wil not be displayed. BUT when i do this:
let input = this.$refs['email'].$children[0];
input.errors.add({ field: 'email', msg: 'email already is use'});
// this.loading = false;
So this.loading will NOT get set, then the error will get set and displayed in my view.
But I want this.loading still be false because I want my loading icon not be displayed. Anyone have a explanation about this.
EDIT: More code
methods: {
register: function () {
let anyError = false;
this.$validate(this, ['first_name', 'last_name', 'phone', 'email', 'password'], function (value, last_item) {
this.loading = true;
if (value === false) {
anyError = true;
}
if (anyError || !last_item) {
return;
}
this.$http.post('v1/auth/register', {
first_name: this.first_name,
last_name: this.last_name,
email: this.email,
phone: this.phone,
password: this.password
}).then((response) => {
this.registration_card = 2;
}).catch((error) => {
if (error.data.error.message === "email_already_exists") {
let input = this.$refs['email'].$children[0];
input.errors.add({field: 'email', msg: 'email already is use'});
this.loadingTest = false;
console.log(input.errors);
console.log(this.loadingTest);
}
});
}.bind(this));
},
}
this.$validate does this:
export default function(scope, arrayOfValues, callback) {
let total = arrayOfValues.length - 1;
let last_item = false;
arrayOfValues.forEach(function(value, index) {
let input = scope.$refs[value].$children[0];
input.$validator.validate().then(value => callback(value, total === index, index));
});
}
I do this because i have custom input components
EDIT: this is where i am using loading:
<j-button label="Register" :loading="loading" #click.native="register"/>
And button coomponent is:
<template>
<button type="button">
<span v-if="!loading">{{label}}</span>
<loading v-if="loading"/>
</button>
</template>
<script>
import loading from 'vue-loading-spinner/src/components/Circle'
export default {
name: 'j-button',
props: [
'label',
'loading'
],
components: {
loading
}
}
</script>
EDIT: Even more code!!!!!
My j-input component
<template>
<div>
<label v-bind:class="{ 'active': (newValue.length > 0)}">{{label}}</label>
<input v-bind:class="{ 'error': (errors.has(name))}" type="text" :name="name" v-validate="rules" :placeholder="label" v-model="newValue" v-on:input="updateValue()" ref="input">
<span v-if="errors.has(name)">{{errors.first(name)}}</span>
</div>
</template>
<script>
export default {
name: 'j-text',
inject: ['$validator'],
props: [
'label',
'name',
'rules',
'value',
],
data() {
return {
newValue: ''
}
},
created() {
this.newValue = this.value;
this.updateValue();
},
methods: {
updateValue: function () {
this.$emit('input', this.newValue);
},
}
}
</script>
So i have found the issue and it is still very strange why. I will make another question for this. Its about my j-button component:
<template>
<button type="button">
<span v-if="!loading">{{label}}</span>
<loading v-if="loading"/>
</button>
</template>
<script>
import loading from 'vue-loading-spinner/src/components/Circle'
export default {
name: 'jellow-button',
props: [
'label',
'loading'
],
components: {
loading
}
}
</script>
To fix this weird issue I had to change this:
<loading v-if="loading"/>
To:
<loading v-show="loading"/>
If I changed this, then the error will be loaded and the button loading icon will be turned off doing this in my catch:
}).catch(error => {
if(error.data.error.message === "email_already_exists") {
let input = this.$refs['email'].$children[0];
input.errors.add({field: 'email', msg: 'email already in use'});
this.loading = false;
}
});
But again. If I do the v-if instead of the v-show in my button then the error will not be showing. Very strange. I will create another question and I hope I get a answer on that.
This is very simple. Only reference change refreshes Vue view.
When you do this:
new Vue({
data: ['property'],
method: {
change() {
this.property = "yes"; // will get refreshed
}
}
});
The view gets refreshed (changes are displayed). But when you change the object's field reference (not the object itself) or when you call a method on it, it won't get refreshed.
change() {
this.property.field = "yes"; // won't get refreshed
this.property.mtehod("yes"); // won't get refreshed
}
Only some certain methods (like array.push()) are tweaked by Vue.js to recognize that those methods get view refreshed. If you want to make it work you need to call this.$forceUpdate() or use Vue.set() to change vales.
So when you add your errors, the view won't get refreshed, only when you change your data property (loading) the view recognize that data value changed and refreshed your view.
Please read Reactivity in Depth, especially chapter "How Changes Are Tracked". Please see which ways of setting data are reactive and which aren't.
I'm using vuex to manage the state in my application and doing one way binding with my form.
<script>
import { mapGetters } from 'vuex'
import store from 'vuex-store'
import DataWidget from '../../../../uiComponents/widget'
export default {
data () {
return {
isEdit: false,
msg: {
id: 0,
content: '',
isEnabled: false
}
}
},
components: {
DataWidget
},
computed: mapGetters({
messageId: 'messageId',
messageContent: 'messageContent',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
methods: {
onSave () {
store.dispatch('saveMessage', this.msg, { root: true })
if (this.isMessageValid) {
this.isEdit = !this.isEdit
}
}
},
created () {
this.msg.id = this.messageId
this.msg.content = this.messageContent
this.msg.isEnabled = this.isMessageEnabled
}
}
</script>
<b-form-textarea id="content" v-model="msg.content" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
On load, the values on created() are not binded until I perform an action on the page or refresh the page.
I have tried mounted () hooked same thing.
My Vuex store (Message Module) looks like this:
const state = {
messageId: 0,
messageContent: '',
isMessageEnabled: false,
isMessageValid: true
}
const getters = {
messageId: state => state.messageId,
messageContent: state => state.messageContent,
isMessageEnabled: state => state.isMessageEnabled,
isMessageValid: state => state.isMessageValid
}
const actions = {
getMessage ({commit, rootGetters}) {
api.fetch('api/Preference/Message', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data)
})
}
}
const mutations = {
[types.MESSAGE_LOAD] (state, payload) {
state.messageId = payload ? payload.id : 0
state.messageContent = payload ? payload.content : ''
state.isMessageEnabled = payload ? payload.enabled : false
}
}
export default {
state,
getters,
actions,
mutations
}
and I have a global action (action.js) the gets multiple data:
export const loadSetting = ({ commit, rootGetters }) => {
api.fetchAsync('api/Preference/all', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data.message)
commit(types.HELPDESK_LOAD, data.helpDesk)
commit(types.VOLUME_LOAD, data.volumes)
commit(types.DOWNLOAD_LOAD, data.downloadService)
})
}
My api call:
async fetchAsync (url, token = '') {
let data = await axios.get(HOST + url, {
headers: {
'Authorization': 'bearer ' + token
}
})
return data
}
The problem is your'e calling an async method in Vuex but in the created method, you're treating it like a sync operation and expect to get a value.
You need to use the computed properties you created since they are reactive and will update on every change. In order to make the computed writeable change it to be like this:
computed: {
...mapGetters({
messageId: 'messageId',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
messageContent(){
get () {
return this.$store.getters.messageContent
},
set (value) {
//this is just an example, you can do other things here
this.$store.commit('updateMessage', value)
}
}
}
And change the html to use messageContent:
<b-form-textarea id="content" v-model="messageContent" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
For more info refer to this: https://vuex.vuejs.org/en/forms.html