Convert startdate and enddate to unix time with react-jsonschema-form - javascript

I use this library to build my forms: https://github.com/mozilla-services/react-jsonschema-form and I have a uischema for date inputs, but when I send the form I want to convert startdate and enddate in unix time.
Here is the code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
import React, { Component, FormGroup, FormControl,ControlLabel } from 'react';
import NavBar from '../Header/NavBar';
import * as dropdown from "../../helpers/dropdown";
import Form from "react-jsonschema-form";
import * as formSubmit from "../../helpers/sumary";
import * as moment from 'moment';
class Sumary extends Component {
constructor(props) {
super(props);
this.state = {
activityschema: {},
machineschema: {},
activitiesTypes: [],
startDate: moment(),
endDate: moment(),
activityType: 0,
step: 0
};
}
componentWillMount = async () => {
const activitiesTypes = await dropdown.getActivitiesType();
const machines = await dropdown.getMachines();
this.setState({
activityschema: {
type: "object",
properties: {
typeNameId: {
type: 'number',
title: 'Type:',
enum: activitiesTypes.map((item) => {
return item.id;
}),
enumNames: activitiesTypes.map((item) => {
return item.name;
})
},
startDate: {type: "string", title: 'Start date:'},
endDate: {type: "string", title: 'End date:'},
}
},
machineschema: {
type: "object",
properties: {
machinesId: {
type: 'number',
title: 'Machine Name:',
enum: machines.map((item) => {
return item.id;
}),
enumNames: machines.map((item) => {
return item.name;
})
},
//startDate: {type: "string", title: 'Start date:'},
//endDate: {type: "string", title: 'End date:'},
}
},
uiSchema: {
startDate: {
"ui:widget": "date"
},
endDate: {
"ui:widget": "date"
}
}
});
};
onSubmit = async ({formData}) => {
if (formData.typeNameId === 10) {
this.setState({
step: 1,
formData: {
...this.state.formData,
...formData
},
});
await formSubmit.getSumary(formData);
console.log(formData);
} else {
await formSubmit.getSumary(formData);
console.log(formData);
}
};
render () {
let schema= null;
schema = this.state.activityschema;
let uiSchema = this.state.uiSchema;
switch(this.state.step){
case 1:
schema = this.state.machineschema;
break;
default:
break;
}
return (
<Form
schema={schema}
uiSchema={uiSchema}
onSubmit={this.onSubmit}
formData={this.state.formData} />
);
}
}
export default Sumary;
The code above works fine, but when I submit data, my formData in startDate and endDate are on this type of format:
Object {typeNameId: 3, startDate: "2017-12-22", endDate: "2017-12-27"}
But I want them in unix time.
I tryed something like this:
onSubmit = async ({formData}) => {
this.state.formData.startDate.unix();
this.state.formData.endDate.unix();
if (formData.typeNameId === 10) {
this.setState({
step: 1,
formData: {
...this.state.formData,
...formData
},
});
await formSubmit.getSumary(formData);
console.log(formData);
} else {
await formSubmit.getSumary(formData);
console.log(formData);
}
};
But this will get:
Unhandled Rejection (TypeError): Cannot read property 'startDate' of
undefined

So I see following problems with your code:
You don't have formData in your React initial state.
If you want to use formData with your state you need to track onChange event to get the latest data
In your onSubmit if you use formData that you get in onSubmit call, it should be enough to remove Rejection error
Date has a format of string, so it's not exactly moment object, you need to construct it from the string and convert to unix format
If you want to input with type date you need to specify format on your schema object, otherwise it's just a text input field
You are right in your approach, there seems to be no straight forward way to do this, without adding additional widgets or fields.
Hope this helps.

Related

Mongoose: pre('validate') middleware is not working

This is my mongoose Setup. This happens only when i use class syntax. When i do the same thing with the use of functional programming it works fine. This is the first time i am using class syntax to do this. I think that's where the problem lies. I am doing something wrong with my class definition.
This is my mongoose Setup. This happens only when i use class syntax. When i do the same thing with the use of functional programming it works fine. This is the first time i am using class syntax to do this. I think that's where the problem lies. I am doing something wrong with my class definition.
const mongooseService = require('./services/mongoose.service')
const slugify = require('slugify')
const { marked } = require('marked')
const createDomPurifier = require('dompurify')
const { JSDOM } = require('jsdom')
const dompurify = createDomPurifier(new JSDOM().window)
class ArticleDao {
Schema = mongooseService.getMongoose().Schema
articleSchema = new this.Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
},
markdown: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: new Date(),
},
slug: {
type: String,
required: true,
unique: String,
},
sanitizedHtml: {
type: String,
required: true,
},
})
Article = mongooseService.getMongoose().model('Article', this.articleSchema)
constructor() {
console.log(`created new instance of DAO`)
this.setPreValidation()
}
setPreValidation() {
console.log('h')
this.articleSchema.pre('save', (next) => {
if (this.title) {
this.slug = slugify(this.title, { lower: true, strict: true })
}
if (this.markdown) {
this.sanitizedHtml = dompurify.sanitize(marked(this.markdown))
}
next()
})
}
async addArticle(articleFields) {
const article = new this.Article(articleFields)
await article.save()
return article
}
async getArticleById(articleId) {
return this.Article.findOne({ _id: articleId }).exec()
}
async getArticleBySlug(articleSlug) {
return this.Article.findOne({ slug: articleSlug })
}
async getArticles() {
return this.Article.find().exec
}
async updateArticleById(articleId, articleFields) {
const existingArticle = await this.Article.findOneAndUpdate({
_id: articleId,
$set: articleFields,
new: true,
}).exec()
return existingArticle
}
async removeArticleById(articleId) {
await this.Article.findOneAndDelete({ _id: articleId }).exec()
}
}
module.exports = new ArticleDao()
This is the error i get:
Article validation failed: sanitizedHtml: Path `sanitizedHtml` is required., slug: Path `slug` is required.

VUEJS: Component to update a users communication preference

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.

Quasar QSelect is not opening when performing AJAX call

I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.
Problem
Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.
video of the problem
As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.
Code
import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "#/services/api/resources/tags/codec";
import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from "quasar";
export const TagAutoComplete = defineComponent({
name: "TagAutoComplete",
props: {
modelValue: { type: Array as PropType<TagCodec[]> },
},
emits: ["update:modelValue"],
setup(props, context) {
const loading = ref(false);
const tags = ref<TagCodec[]>([]);
// eslint-disable-next-line #typescript-eslint/ban-types
const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
const parameters = val === "" ? {} : { title: val };
doneFn(async () => {
loading.value = true;
const response = await list(parameters);
if (val) {
const needle = val.toLowerCase();
tags.value = response.data.data.filter(
(tag) => tag.title.toLowerCase().indexOf(needle) > -1
);
} else {
tags.value = response.data.data;
}
loading.value = false;
});
};
const onInput = (values: TagCodec[]) => {
context.emit("update:modelValue", values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: "title",
optionValue: "id",
outlined: true,
useInput: true,
useChips: true,
placeholder: "Start typing to search",
onFilter: onFilterTest,
"onUpdate:modelValue": onInput,
loading: loading.value,
});
};
},
});
What I have tried
I have tried to use the several props that is available for the component but nothing seemed to work.
My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.
Questions
Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?
It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.
const onFilterTest = async (val, update /* abort */) => {
const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await list(parameters);
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
I tested it by the following code and mocked values.
// import type { PropType } from 'vue';
import { defineComponent, h, ref } from 'vue';
// import type { TagCodec } from "#/services/api/resources/tags/codec";
// import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from 'quasar';
export const TagAutoComplete = defineComponent({
name: 'TagAutoComplete',
props: {
modelValue: { type: [] },
},
emits: ['update:modelValue'],
setup(props, context) {
const loading = ref(false);
const tags = ref([]);
const onFilterTest = async (val, update /* abort */) => {
// const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
data: [
{
id: 1,
title: 'Vue',
},
{
id: 2,
title: 'Vuex',
},
{
id: 3,
title: 'Nuxt',
},
{
id: 4,
title: 'SSR',
},
],
},
});
}, 3000);
});
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
const onInput = (values) => {
context.emit('update:modelValue', values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: 'title',
optionValue: 'id',
outlined: true,
useInput: true,
useChips: true,
placeholder: 'Start typing to search',
onFilter: onFilterTest,
'onUpdate:modelValue': onInput,
loading: loading.value,
});
};
},
});

Update array of object state in react js

I have the following code snippet from my component where I generate Input field according to the objects in the state.
I can successfully generate the input fields but have been getting error message:
TypeError: Cannot read property 'map' of undefined
Pointing to the method arrayObjToArrary in Utils.js whenever I type in the input field.
How can I update the value of here ??
Main.js
import Input from "../UI/Input";
import {arrayObjToArrary} from "../../utility/Utils.js";
const [profiles, setProfiles] = useState({
controls: [
{
network: {
elementType: "input",
elementConfig: {
type: "text",
label: "Network",
},
value: "Twitter",
},
},
{
username: {
elementType: "input",
elementConfig: {
type: "text",
label: "Username",
},
value: "#john",
},
},
{
url: {
elementType: "input",
elementConfig: {
type: "url",
label: "URL",
},
value: "https://example.xyz",
},
},
],
});
const profilesControls = arrayObjToArrary(profiles.controls);
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier] = event.target.value;
setProfiles(list);
};
let profileField = profilesControls.map((formElement) => (
<Input
label={formElement.config.elementConfig.label}
key={formElement.index}
type={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
changed={(event) => arrayInputHandler(event, formElement.index, formElement.id)}
/>
));
Utils.js
export const arrayObjToArrary = (controls) => {
const formElementsArray = controls.map((item,index) =>({
id: Object.keys(item)[0],
index:index,
config: item[Object.keys(item)[0]],
}))
return formElementsArray;
}
You can try this
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier].value = event.target.value;
setProfiles({ controls: list });
};
check here codesandbox
The issue in how you update your profiles object in arrayInputHandler. When you pass in the list to setProfiles, it changes its structure from an object to array.
Also you must not mutate the original state values. The correct way to update is as below
const arrayInputHandler = (event, index, identifier) => {
const value = event.target.value;
setProfiles(prev => ({
...prev,
controls: profiles.controls.map((controls, i) => {
if(i === index) {
return {
...controls, [identifier]: {
...controls[identifier],
value
}
}
}
return controls
});
}));
};
P.S. You can always solve your problem in a simplistic manner like
const arrayInputHandler = (event, index, identifier) => {
const list = [...profiles.controls];
list[index][identifier] = event.target.value;
setProfiles({profile:list});
};
However its not a correct approach and should be avoided as react relies a lot on immutability for a lot of its re-rendering and other optimizations

Typescript reducer issue

I have the following:
...
type RepairsState = {
data: Property[] /* Property is an object coming from another file */
}
type RepairsPropertyLoadAction = {
type: typeof REPAIRS_PROPERTY_LOAD
payload: { models: Property[] } /* the payload will return an object that has a models property of an array of objects that match my property type */
}
/* Reducer */
export const initialState: RepairsState = {
data: [
{
id: '',
jobNo: '',
trade: '',
priority: '',
status: '',
raisedDate: new Date(),
appointmentDate: new Date(),
completedDate: new Date(),
description: ''
}
]
}
export default function reducer(state = initialState, action: RepairsPropertyLoadAction): RepairsState {
switch (action.type) {
case REPAIRS_PROPERTY_LOAD:
console.log(action.payload)
return {
...state,
data: action.payload
}
default:
return state
}
}
export const getRepairsProperty = (state: AppState) => state.repairs.data
...
Property class:
export default class Property {
id: string = ''
jobNo: string = ''
trade: string = ''
priority: string = ''
status: string = ''
raisedDate: Date = new Date()
appointmentDate: Date = new Date()
completedDate: Date = new Date()
description: string = ''
}
however I am getting the following error:
Type '{ models: Property[]; }' is missing the following properties from type 'Property[]': length, pop, push, concat, and 28 more. TS2740
The action returns an {models: Property []} object for the data, but the state has data: Property []
return {
...state,
data: action.payload.model
}
You are missing the models property, which is defined as part of RepairsPropertyLoadAction. The reducer should be returning this instead:
return {
...state,
data: action.payload.models,
}

Categories