Happy New Year...
I am extremely new to RN and am building a small app to get a feel for it.
I am trying to figure out if there is a way to clean this part up.
Below I have a nested multidimensional object state in the constructor and a reset function.
I have four input fields and a plain text area which I update dynamically based on a result.
Now whilst this works it feels not so clean, say if I wanted to add another nested multidimensional object state which sets different default values on reset I am going to have to add another if block to handle that so the it compounds the problem even further.
Any thoughts how to improve this or am I going about it the wrong way :/
constructor(props) {
super(props)
this.state = {
input1: '',
input2: '',
input3: '',
input4: '',
result: {
'ratio': 0,
'style': '',
},
}
}
reset() {
let newState = {};
for (const field of Object.keys(this.state)) {
if (field == 'result') {
this.setState({
result: {
ratio: 0,
style: '',
}
});
continue;
}
newState[field] = '';
}
this.setState(newState);
}
** edit **
To make it clearer if I add another multidimensional object to the state I will need to include another if statement
for (const field of Object.keys(this.state)) {
if (field == 'reset') {
// ...
}
if (field == 'extra') {
// ...
}
newState[field] = '';
}
Ideally what I need is a copy of this.state before its updated then just restore the copy which has the default parameters this.setState(copy)
Many thanks.
I am not entirely sure what you mean by 'nested', but here are a couple of suggestions:
reset() {
let newState = {};
for (const field of Object.keys(this.state)) {
if (field == 'result') {
newState.result = {
ratio: 0,
style: '',
};
}else{
newState[field] = '';
}
}
this.setState(newState);
}
Looping this way you avoid triggering multiple renders since you only call setState once.
Edit: If you want to avoid checking for fields with known names, you can just access those directly. 'Wrap' all your inputs in a new entry in the state, and just loop through that one:
constructor(props) {
super(props)
this.state = {
inputs:{
input1: '',
input2: '',
input3: '',
input4: '',
},
result: {
'ratio': 0,
'style': '',
},
extra = {
extraKey: 1,
}
}
}
reset() {
let newState = {};
newState.result = {ratio: 0, style: ''};
newState.extra = // ...
newState.inputs = {};
for (const field of Object.keys(this.state.inputs)) {
newState.inputs[field] = '';
}
this.setState(newState);
}
It is a bit cumbersome to use a loop to update the keys in an object, but I cannot think of a simpler solution when the key names are not known in advance.
Related
I need to check whether my input field is empty or not.
Logic
if form.name has value, use increase function
if form.name is empty, use decrease function
do not use increase, decrease functions on each character that user inputs or removes
Code
<el-form-item label="Product name *">
<el-input v-model="form.name"></el-input>
</el-form-item>
methods: {
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
any idea?
Update
Script
data() {
return {
form: {
name: '', // require
slug: '',
price: '', // require
supplier_id: '', // require
new_price: '',
base_price: '',
sku: '',
qty: 1, // require
active: '', // require
photo: '',
photos: [],
shortDesc: '',
longDesc: '',
origin: '',
tags: [],
brand_id: '', // require
categories: [],
user_id: '',
seoTitle: '',
seoTags: '',
seoPhoto: '',
seoDescription: '',
variations: [],
options: [],
condition: '', // require
isbn: '',
ean: '',
upc: '',
height: '',
weight: '',
lenght: '',
width: '', // require
},
}
},
methods: {
onSubmit(e) {
e.preventDefault();
axios.post('/api/admin/products/store', this.form)
.then(res => {
// do my things
})
.catch(function (error) {
console.log('error', error);
});
},
}
HTML
<el-form ref="form" :model="form" label-width="120px" enctype="multipart/form-data">
// my inputs (listed in form part in script above)
<el-button type="primary" #click="onSubmit" native-type="submit">Create</el-button>
</el-form>
One possible solution would be to use #focus and #blur events to check if form.name has a value before increasing or decreasing, this would be fired on focus or on blur events, so you will not have the methods fired on each character input or deletion.
for example:
<el-form-item label="Product name *">
<el-input #focus="checkName" #blur="checkName" v-model="form.name"></el-input>
</el-form-item>
methods: {
checkName() {
//If form.name has a value then run increase method, otherwise run decrease method
!!this.form.name ? this.increase() : this.decrease()
},
increase() {
this.percentage += 8.3;
if (this.percentage > 100) {
this.percentage = 100;
}
},
decrease() {
this.percentage -= 8.3;
if (this.percentage < 0) {
this.percentage = 0;
}
},
}
You can see a working fiddle HERE
UPDATE
Alright so i did follow the rules you state on your question, and i didn't know you wanted to get the percentage of completion of the form, so in order to do that, i would suggest to use a computed property, you can read more about computed properties in the VueJS Documentation, this way the percentage is calculated based on the criteria we can give it, and only if the form has values.
computed: {
formProgress: function () {
let totalInputs = Object.keys(this.form).length;
let filledInputs = 0;
Object.values(this.form).forEach(val => {
if (!!val){
filledInputs++;
}
});
return (filledInputs/totalInputs)*100
}
},
As you can see in one single computed property you can handle the complex logic and return the value reactively, to explain it better, i'm counting the lenght of the form object, to get total number of inputs in your form, so it's important to have all your form data inside the form data object, then i convert that object to an array to iterate it, and i check if each property has a value on it, if does it, i add 1 to the filledInputs counter, and finally just return a simple math to get the percentage. please check the new Fiddle here to see it in action:
FORM PROGRESS FIDDLE
If you have any other doubt just let me know.
UPDATE 2:
All right in order to only count for specific inputs for the form progress, i have modified the code to work based on an array that contains the names of the properties that are required. here is the full code:
data() {
return {
form: {
name: '',
lastName: null,
categories: [{}],
},
requiredFields: ['name', 'categories']
};
},
computed: {
formProgress: function () {
let totalInputs = this.requiredFields.length;
let filledInputs = 0;
Object.entries(this.form).forEach(entry => {
const [key, val] = entry;
if (this.requiredFields.includes(key)){
switch (val.constructor.name) {
case "Array":
if (val.length !== 0){
if (Object.keys(val[0]).length !== 0){
filledInputs++;
}
}
break
case "Object":
if (Object.keys(val).length !== 0){
filledInputs++;
}
break
default:
if (!!val){
filledInputs++;
}
}
}
});
return Number((filledInputs/totalInputs)*100).toFixed(1)
}
},
And here is the updated FIDDLE
As you can see now i'm using Object.entries to get the key and value of the form object, so you can have a single form object to send to your backend, this way i'm checking first if the key is in the required fields, and if has a value, so all you need to do is update the requiredFields data array with the same names as your inputs data property to make the validation work, also there is a validation depending if is array, array of objects, or object, that way it will validate input on each data type.
Hope this works for you.
I am trying to get the array values from
"validateBeforeSubmit" function to "saveForm" function. But I am
getting values of "undefined" in "arrlength". Please help me to solve.
This my code in vue.js
export default {
name: '',
data() {
return {}
},
ready: function() {
this.validateBeforeSubmit()
this.saveForm();
},
methods: {
validateBeforeSubmit() {
var fieldsVal = new Array();
var firstName = document.getElementById('firstName').value
var lastName = document.getElementById('lastName').value
var designation = document.getElementById('designation').value
if (firstName != "" && lastName != "" && designation != "") {
fieldsVal.push(firstName);
fieldsVal.push(lastName);
fieldsVal.push(designation);
return fieldsVal;
} else {
fieldsVal.length = 0;
return fieldsVal;
}
return fieldsVal;
},
saveForm() {
var fieldsValArray = this.validateBeforeSubmit();
var arrLength = fieldsValArray.length;
}
}
}
I can see multiple issues in your code:
1) Don't apply jQuery-like approach for getting input values. Use v-model instead. This will simplify your code
<template>
<input v-model="form.firstName" type="text"/>
</template>
<script>
export default {
data: {
form: {
firstName: '',
}
},
methods: {
validateBeforeSubmit() {
// take `firstName` directly from `data` not need for `getElementById`
const firstName = this.form.firstName;
}
},
}
</script>
2) Remove validateBeforeSubmit and saveForm from ready. Ready hook is obsolete in vue#2. And also it makes no sense. It's better to call it on form #submit.
3) It's better to create array using [] syntax instead of new Array()
Why never use new Array in Javascript
4) Always provide name for your component for easier debug
export default {
name: 'ValidationForm',
}
5) I don't know where was an issue but it works. Check this link below. I have updated your code. Try to submit form and check the console:
https://codesandbox.io/s/w6jl619qr5?expanddevtools=1&module=%2Fsrc%2Fcomponents%2FForm.vue
In my reducer, suppose originally I have this state:
{
"loading": false,
"data": {
"-L1LwSwW97KkwdSnYvsc": {
"likeCount": 10,
"liked": false, // I want to update this property
"commentCount": 5
},
"-L1EY2_fqzn7sM1Mbf_F": {
"likeCount": 8,
"liked": true,
"commentCount": 22
}
}
}
Now, I want to update liked property inside -L1LwSwW97KkwdSnYvsc object, which is inside data object and make it true. This is what I've been trying, but apparently, it's wrong, because after I've updated the state, the componentWillReceiveProps function inside a component that listens to the state change does not get triggered:
var { data } = state;
data['-L1LwSwW97KkwdSnYvsc'].liked = !data['-L1LwSwW97KkwdSnYvsc'].liked;
return { ...state, data };
Could you please specify why it's wrong and how I should change it to make it work?
You're mutating state! When you destructure:
var { data } = state;
It's the same as:
var data = state.data;
So when you do:
data[…].liked = !data[…].liked
You're still modifying state.data which is in turn mutating state. That's never good - use some nested spread syntax:
return {
...state,
data: {
...state.data,
'-L1LwSwW97KkwdSnYvsc': {
...state.data['-L1LwSwW97KkwdSnYvsc'],
liked: !state.data['-L1LwSwW97KkwdSnYvsc'].liked
}
}
};
Using spread operator is good until you start working with deeply nested state and/or arrays(remember spread operator does a shallow copy only).
I would rather recommend you starting working with immutability-helper instead. It is a React recommendation and it will let your code more readable and bug free.
Example:
import update from "immutability-helper";
(...)
const toggleLike = !state.data["-L1LwSwW97KkwdSnYvsc"].liked
return update(state, {
data: {
"-L1LwSwW97KkwdSnYvsc": {
like: {
$set: toggleLike
}
}
}
})
I have some data in vuejs that I want to format before sending it off through an ajax call but it changes the view its bound to. For example I have a birthday field that is formatted like this on the view 01/11/1981 but I need to format that to YYYY-MM-DD HH:mm:ss for the db and I don't want to do this on the backend.
Where and when would I do this on the frontend? I have tried doing this before the ajax request and it changes the view, so I made a copy of the data and modified it and that also changed the view. It seems no matter what I do it affects the view.
Here is my methods block:
methods: {
/**
* Update the user's contact information.
*/
update() {
/*Attempt to copy and format*/
var formattedForm = this.form;
formattedForm.birthday = moment(formattedForm.birthday).format('YYYY-MM-DD HH:mm:ss');```
Spark.put('/settings/contact', formattedForm)
.then(() => {
Bus.$emit('updateUser');
});
},
}
Here is my data block as well:
data() {
return {
form: $.extend(true, new SparkForm({
gender: '',
height: '',
weight: '',
birthday: '',
age: '',
}), Spark.forms.updateContactInformation),
};
},
The easiest way is to make a clone using Object.assign, like so:
let form = Object.assign({}, this.form);
form.age = 21;
Here's the JSFiddle: https://jsfiddle.net/y51yuf05/
Objects are passed by reference in javascript, which means:
let a = {
"apple": 6
}
let b = a
then, b and a are pointing to the same location in the memory, it is essentially copying the address of the object in a to the variable b.
You need to therefore clone the object, there are many ways to do it like:
b = Object.assign({}, a)
MDN: Object.assign()
this would not be deeply cloned, which means if your object is nested then the nested objects would still be linked between the original and the copy.
for which I use:
function isObject(obj) {
return typeof obj === 'object' && !Array.isArray(obj)
}
function clone(obj) {
let result = {}
for (let key in obj) {
if (isObject(obj[key])) {
result[key] = clone(obj[key])
} else {
result[key] = obj[key]
}
}
return result
}
function logger () {
console.log("p.a.b.c: ", p.a.b.c)
console.log("q.a.b.c:", q.a.b.c)
console.log("r.a.b.c:", r.a.b.c)
}
let p = {a: {b: {c: 5}}}
let q = clone(p)
let r = Object.assign({}, p)
logger()
p.a.b.c = 11
logger()
I've got a list I'm trying to pull an object from using _.get but following that selection I need to loop over the object to create a new property. So far I've been successful using a combination of _.get and _.map as shown below but I'm hoping I can use _.chain in some way.
var selected = _.get(results, selectedId);
return _.map([selected], result => {
var reviews = result.reviews.map(review => {
var reviewed = review.userId === authenticatedUserId;
return _.extend({}, review, {reviewed: reviewed});
});
return _.extend({}, result, {reviews: reviews});
})[0];
Is it possible to do a transform like this using something other than map (as map required me to break this up/ creating an array with a solo item inside it). Thank you in advance!
I can see that you're creating unnecessary map() calls, you can simply reduce all those work into something like this:
var output = {
reviews: _.map(results[selectedId], function(review) {
return _.defaults({
reviewed: review.userId === authenticatedUserId
}, review);
})
};
The defaults() method is similar to extend() except once a property is set, additional values of the same property are ignored.
var selectedId = 1;
var authenticatedUserId = 1;
var results = {
1: [
{ userId: 1, text: 'hello' },
{ userId: 2, text: 'hey' },
{ userId: 1, text: 'world?' },
{ userId: 2, text: 'nah' },
]
};
var output = {
reviews: _.map(results[selectedId], function(review) {
return _.defaults({
reviewed: review.userId === authenticatedUserId
}, review);
})
};
document.body.innerHTML = '<pre>' + JSON.stringify(output, 0, 4) + '</pre>';
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>