React json schema form relations - javascript

From the step 1 dropdown if the user is picking one after submit redirect it to step2schema, and if he is picking two redirect him after submit to step3schema. Here is the jsfiddle (check my link):
const Form = JSONSchemaForm.default;
const step1schema = {
title: "Step 1",
type: "number",
enum: [1, 2],
enumNames: ["one", "two"]
};
const step2schema = {
title: "Step 2",
type: "object",
required: ["age"],
properties: {
age: {type: "integer"}
}
};
const step3schema = {
title: "Step 3",
type: "object",
required: ["number"],
properties: {
number: {type: "integer"}
}
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {step: 1, formData: {}};
}
onSubmit = ({formData}) => {
if (this.state.step === 1) {
this.setState({
step: 2,
formData: {
...this.state.formData,
...formData
},
});
} else {
alert("You submitted " + JSON.stringify(formData, null, 2));
}
}
render() {
return (
<Form
schema={this.state.step === 1 ? step1schema : step2schema}
onSubmit={this.onSubmit}
formData={this.state.formData}/>
);
}
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://npmcdn.com/react-jsonschema-form#1.0.0/dist/react-jsonschema-form.js"></script>
<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>
https://jsfiddle.net/bogdanul11/sn4bnw9h/29/
Here is the lib I used for documentation: https://github.com/mozilla-services/react-jsonschema-form
What I need to change in order to have my desired behavoir ?

Replace your state to this first
this.state = {step: 0, formData: {}};
Then replace your onSubmit logic to
onSubmit = ({formData}) => {
const submitted = formData;
this.setState({
step: submitted,
formData: {
...this.state.formData,
...formData
},
})
}
Finally replace your schema logic
schema={this.state.step === 0 ? step1schema : ( this.state.step === 1 ? step2schema : step3schema)}
the schema selecting ternary operation is what we call a compound ternary operation. If state.step is 0 , we select step1schema... if not a new selection statement ( this.state.step === 1 ? step2schema : step3schema) is run; in which if step is 1, step2schema is selected and for anything else step3schema.
Lets say you have more than 3 schemas and you want to access them. For that you will have to define the schemas in an array and access them using the index.
schemaArray = [ {
// schema1 here
} , {
// schema2 here
} , {
// schema3 here
}, {
// schema4 here
}, {
....
}]
Now your schema selecting you will have to use
schema = { schemaArray[ this.state.step ] }

Related

vue-autosuggest output object-object on twice click

by the way this question very similar on Vue algolia autosuggest on select showing [Object object]
but i still did not solve here is my debug or code
<b-form-group
label="Name Agen"
label-for="vi-agen-name"
>
<span v-text="form.agen.name_agen" />
<vue-autosuggest
id="vi-agen-name"
v-model="form.agen.name_agen"
:suggestions="[{ data: form.agen.agens }]"
:limit="7"
:input-props="{
id: 'autosuggest__input',
class: 'form-control',
placeholder: 'Name Agen',
}"
#selected="onSelectedFrom"
#input="searchForm($event, 'agen/', 'agen', 'name_agen')"
>
<template slot-scope="{suggestion}">
<span class="my-suggestion-item">{{ suggestion.item.name_agen }}</span>
</template>
</vue-autosuggest>
</b-form-group>
my problem:
i have on typing yogi on form input
select item that show on suggesstion : [ { data : [ { name_agen : 'Yogi' .....
<span v-text="form.agen.name_agen" /> // output : Yogi
form input // output : yogi
but when i tried to type again on form input yogiabc
thats not show any suggestion so i remove by backspace so the input now is yogi
then i tried select again
the unexpected on twice select :
<span v-text="form.agen.name_agen" /> // output : Yogi // why this result is string
form input // output : object-object // but on form input is an object-object ?
function code:
async onSelectedFrom(option) {
const model = this.form.currentModel
const fieldSuggest = this.form.currentFieldSuggest
const currentLoadData = this.form[`${model}`][`${model}s`]
const currentField = this.form[`${model}`][`${fieldSuggest}`]
this.form[`${model}`] = {
isNew: false,
[`${model}s`]: currentLoadData,
...option.item,
}
console.log('selected', this.form[`${model}`], 'Name Agen:', currentField)
},
searchForm(keyword, uri, model, currentSuggest) {
this.form.currentModel = model
this.form.currentFieldSuggest = currentSuggest
if (keyword) {
clearTimeout(this.timeoutDebounce)
this.timeoutDebounce = setTimeout(() => {
useJwt.http.get(`${uri}`, { params: { keyword, single_search: true } })
.then(response => {
// test debug
if (response.data.total_items === 0) {
// no data
this.form[`${model}`].isNew = true
this.form[`${model}`].user.full_name = null
this.form.isAgenAndTrans = false
this.form[`${model}`][`${model}s`] = []
console.log('no data show', this.form[`${model}`])
} else {
// this.form[`${model}`].isNew = false
this.form.isAgenAndTrans = false
this.form[`${model}`][`${model}s`] = response.data[`${model}s`]
}
}).catch(e => {
this.form[`${model}`].isNew = true
this.form[`${model}`].user.full_name = null
this.form.isAgenAndTrans = false
this.form[`${model}`][`${model}s`] = []
})
}, 300)
}
},
then these the data
data() {
return {
payload: [],
form: {
currentModel: '',
currentFieldSuggest: '',
isAgenAndTrans: false,
agen: {
isNew: true,
id: 0,
name_agen: '',
dm_personal_id: 0,
user: {
full_name: '',
},
agens: [],
},
}
}
}

React useState hook and submit in multi step form

I am creating a multi step form using React JsonSchema Form. I want to update my state every time Next button is clicked on the page and finally Submit. React JsonSchema Form validates the entries only if the button is of type submit. So my Next button is submit button.
As the form will have multiple questions, my state is array of objects. Because of the asynchronous nature of useState, my updated state values are not readily available to save to the backend. How should I get final values?
When I debug I can see the data till previous step. Is there a way to make useState to behave like synchronous call?
Here is the full code:
const data = [
{
page: {
id: 1,
title: "First Page",
schema: {
title: "Todo 1",
type: "object",
required: ["title1"],
properties: {
title1: { type: "string", title: "Title", default: "A new task" },
done1: { type: "boolean", title: "Done?", default: false },
},
},
},
},
{
page: {
id: 1,
title: "Second Page",
schema: {
title: "Todo 2",
type: "object",
required: ["title2"],
properties: {
title2: { type: "string", title: "Title", default: "A new task" },
done2: { type: "boolean", title: "Done?", default: false },
},
},
},
},
];
interface IData {
id: Number;
data: any
};
export const Questions: React.FunctionComponent = (props: any) => {
const [currentPage, setCurrentPage] = useState(0);
const [surveyData, setSurveyData] = useState<IData[]>([]);
const handleNext = (e: any) => {
setSurveyData( previous => [
...previous,
{
id: currentPage,
data: e.formData,
},
]);
if (currentPage < data.length) setCurrentPage(currentPage + 1);
else //Here I want to submit the data
};
const handlePrev = () => {
setCurrentPage(currentPage - 1);
};
return (
<Form
schema={data[currentPage].page.schema as JSONSchema7}
onSubmit={handleNext}
>
<Button variant="contained" onClick={handlePrev}>
Prev
</Button>
<Button type="submit" variant="contained">
Next
</Button>
</Form>
);
};
You can incorporate useEffect hook which will trigger on your desired state change.
Something like this:
useEffect(() => {
// reversed conditional logic
if (currentPage > data.length) {
submit(surveyData);
}
}, [currentPage])
const handleNext = (e: any) => {
setSurveyData( previous => [
...previous,
{
id: currentPage,
data: e.formData,
},
]);
if (currentPage < data.length) setCurrentPage(currentPage + 1);
// remove else
};
On your last submit, you'll have all the previous data in surveyData, but you'll have the latest answer in e.formData. What you'll need to do is combine those and send that to the server.
// in your onSubmit handler
else {
myApiClient.send({ answers: [...surveyData, e.formData] })
}
I would refactor the new state structure to use the actual value of the state and not the callback value, since this will allow you to access the whole structure after setting:
const handleNext = (e: any) => {
const newSurveyData = [
...surveyData,
{
id: currentPage,
data: e.formData
}
];
setSurveryData(newSurveyData);
if (currentPage < data.length) {
setCurrentPage(currentPage + 1);
} else {
// submit newSurveryData
};
};
A side note: you'll also have to account for the fact that going back a page means you have to splice the new survey data by index rather than just appending it on the end each time.

React - update state array

Context
Hi,
I'm creating a form to create virtual machine. Informations are gathered across multiple pages (components) and centralized in top level component.
It's state object looks like this :
const [vmProp, setVmProp] = useState({
name: "",
image_id: "",
image_name: "",
volumesList: [],
RAM: "",
vcpu: "",
autostart: true,
});
Problem
I want to be able to add/remove a volume to the volumes List and I want volumesList to looks like this :
[
{
name: "main",
size: 1024
},
{
name: "backup",
size: 2048
},
{
name: "export",
size: 2048
}
]
What I've tried
For now I've only tried to add a volume.
1 : working but does not produce what I want
const handleAddVolume = (obj) => {
setVmProp({ ...vmProp,
volumesList: {
...vmProp.volumesList,
[obj.name]: {
size: obj.size,
},
} });
};
It's working but the output is :
[
name: {
size: 1024
}
]
2 : should be working but it's not
const handleAddVolume = (obj) => {
setVmProp({ ...vmProp,
volumesList: {
...vmProp.volumesList,
{
name: obj.name,
size: obj.size,
},
} });
};
output :
[
name: "main",
size: 1024
]
Do you have any ideas on how to handle such state object ?
Thanks
You'll have a better time if you don't need to nest/unnest the volumes list from the other state. (Even more idiomatic would be to have a state atom for each of these values, but for simple values such as booleans and strings, this will do.)
const [vmProps, setVmProps] = useState({
name: "",
image_id: "",
image_name: "",
RAM: "",
vcpu: "",
autostart: true,
});
const [volumesList, setVolumesList] = useState([]);
volumesList shouldn't be object, make it as an array inside handleAddVolume function.
Try the below approach,
const handleAddVolume = (obj) => {
setVmProp({ ...vmProp,
volumesList: [
...vmProp.volumesList,
{
name: obj.name,
size: obj.size,
}
] });
};
Here is a working example:
const Example = (props) => {
const inputRef = createRef();
const [vmProp, setVmProp] = useState({
name: "",
image_id: "",
image_name: "",
volumesList: [],
RAM: "",
vcpu: "",
autostart: true,
});
const { volumesList } = vmProp;
const handleAddVolume = (e) => {
e.preventDefault();
const input = inputRef.current.value;
setVmProp((prevVmProp) => {
const newVmProp = { ...prevVmProp };
newVmProp.volumesList.push({
name: input,
size: 1024,
});
return newVmProp;
});
// reset the input
inputRef.current.value = "";
};
return (
<>
<form>
<input ref={inputRef} type="text" />
<button onClick={handleAddVolume}>Add volume</button>
</form>
{volumesList.map((volume) => (
<div key={volume.name}>{`${volume.name} - ${volume.size}`}</div>
))}
</>
);
};
export default Example;
it's just a proof of concept. Basically, setVmProp accepts a function that gets the up-to-date state value as the update is asynchornus and returns the new value of the state. so using ES6's destructuring function, I make a copy of the latest value of vmProp called newVmProp , then push a new object to newVmProp.volumesList then return newVmProp that contains the newly added volume + everything else. The value of the name I get from an input field. Again this is just a proof of concept.
As I needed to be able to add / remove / replace an object in my array I decided to create it's own state as following :
const [volumesList, setVolumesList] = useState([]);
const handleAddVolume = (obj) => {
setVolumesList((oldList) => [...oldList, obj]);
};
const handleRemoveVolume = (obj) => {
setVolumesList((oldList) => oldList.filter((item) => item.name !== obj.name));
};
const handleEditVolume = (obj) => {
setVolumesList(
volumesList.map((volume) =>
(volume.name === obj.name
? { ...volume, ...obj }
: volume),
),
);
};
Thanks for all your answers !

Updating the values in an array of objects in react js

I am trying to merge two objects that I am getting from 2 different api calls(the example here is just a sample). How can I merge the UserId array of object and the userCredentials array together in the user state? I want the state to look like this user:[{id: 1, name"john", country="de"},{id: 2, name"micheal", country="us"}]
...
import React from "react";
import "./styles.css";
export default class App extends React.Component {
constructor() {
super();
this.state = {
user: []
};
}
componentDidMount() {
//api call 1 receiving user Id and name
const UserId = [{ id: 1, name: "john" }, { id: 2, name: "micheal" }];
this.setState({ user: UserId });
//api call 2 receiving userCredentials
const userCredentials = [
{ id: 1, country: "de" },
{ id: 1, country: "us" }
];
this.setState({
user: { ...this.state.user, credentials: userCredentials }
});
}
render() {
console.log("values", this.state);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
</div>
);
}
}
...
my sample code is
https://codesandbox.io/s/fancy-water-5lzs1?file=/src/App.js:0-754
Basically you need to map thru 1 array and find if each object in the array exists in another array and use spread operator and return the merged object in map callback
Working demo
Use the code below:
// option 1 - if you know the keys of the object
let merged = UserId.map(u => {
const user = userCredentials.find(uc => uc.id === u.id);
if (user) u["country"] = user.country;
return u;
});
// option 2 - generic merge
let merged2 = UserId.map(u => {
const user = userCredentials.find(uc => uc.id === u.id);
if (user) return { ...u, ...user };
return u;
});
You could use 'array.concat([])' to merge two array objects together. See bellow example.
let UserId = [{ id: 1, name: "john" }, { id: 2, name: "micheal" }];
const userCredentials = [{ id: 1, country: "de" },{ id: 1, country: "us" }];
const newArray = UserId.concat(userCredentials);
Since you have defined UserId as a const you cannot change it. So you have to make it to let or var to modify the variable.

My static method is not running

Here is my recipe.js file...
const express = require('express');
const mongoose = require('mongoose');
const User = require('../model/user');
require('mongoose-currency').loadType(mongoose);
const Currency = mongoose.Types.Currency;
const Schema = mongoose.Schema;
const reviewSchema = require('../model/review');
let recipeSchema = new Schema({
name: {
type: String,
required: true
},
description: {
type: String,
},
steps: {
type: String,
required: true,
},
ingredients: {
type: Array,
default: ['1', '2', '3', '4']
},
category: {
type: String,
required: true,
index: true
},
postedBy: {
type: String,
required: true,
},
reviewsOfRecipe: [reviewSchema],
numberOfRatings: {
type: Number,
default: 0
},
totalAddedRatings: {
type: Number,
default: 0
},
reviewAverage: {
type: Number,
default: 0
},
postersCreationDate: {
type: Number,
index: true
},
likedBy: {
type: Array
},
reviewedBy: {
type: Array
}
});
// Here is my static method
recipeSchema.methods.calculateAverage = function(){
let recipe = this;
if (recipe.numberOfRatings === 0 && recipe.totalAddedRatings === 0){
recipe.reviewAverage = 0;
}
else {
recipe.reviewAverage = recipe.totalAddedRatings / recipe.numberOfRatings
}
};
let Recipe = mongoose.model('Recipe', recipeSchema);
module.exports = Recipe;
In my router file, every time a user submits a review for a recipe, the fields numberOfRatings and totalAddedRatings get incremented. And after they get incremented, my static method calculateAverage should run and update the document.
Here is what it looks like in my code:
Recipe.findOneAndUpdate({_id: recipe._id, postersCreationDate: recipe.postersCreationDate},{$inc: {numberOfRatings: 1, totalAddedRatings: reviewScore}}, {returnNewDocument: true}).then((recipe) => {
recipe.calculateAverage();
});
However, every time a user submits a review, although numberOfRatings and numberOfRatings get incremented accordingly, reviewAverage does not.
I am thinking about setting reviewAverage as a virtual field instead; but I am worried that doing so would make it harder and inefficient to sort the recipes by the highest and lowest review averages.
first some things about your findOneAndUpdate can change. Id should be sufficient to find by and 'returnNewDocument' is not an option. In mongoose it's just 'new'
Recipe.findOneAndUpdate({_id: recipe._id},{$inc: {numberOfRatings: 1, totalAddedRatings: reviewScore}}, {new: true}).then((recipe) => {
recipe.calculateAverage();
});
The problem is that you aren't saving the average to the recipe.
recipeSchema.methods.calculateAverage = function(callback) {
let recipe = this;
if (recipe.numberOfRatings === 0 && recipe.totalAddedRatings === 0) {
recipe.reviewAverage = 0;
}
else {
recipe.reviewAverage = recipe.totalAddedRatings / recipe.numberOfRatings
}
recipe.save(callback);
};

Categories

Resources