How to properly use Yup schema with single react-select? - javascript

I'm using react-select with Formik and Yup to validate my form, but for some reason my validation is not working. My Schema looks like this:
const Schema = Yup.object().shape({
age: Yup.object().shape({
label: Yup.string().required("Required"),
value: Yup.string().required("Required")
})
});
And my data looks like this:
export const ageOptions = [
{ value: 0.1, label: "Not born yet" },
{ value: 0.3, label: "Baby - 0 to 3 months" },
{ value: 0.6, label: "Baby - 3 to 6 months" },
{ value: 0.12, label: "Baby - 6 to 12 months" },
{ value: 0.18, label: "Baby - 12 to 18 months" },
{ value: 0.24, label: "Baby - 18 to 24 months" },
{ value: 2, label: "2 years" },
{ value: 3, label: "3 years" },
{ value: 4, label: "4 years" },
{ value: 5, label: "5 years" },
{ value: 6, label: "6 years" },
{ value: 7, label: "7 years" },
{ value: 8, label: "8 years" },
{ value: 9, label: "9 years" },
{ value: 10, label: "10 years" },
{ value: 11, label: "11 years" },
{ value: 12, label: "12 years" },
{ value: 13, label: "13 years" },
{ value: 14, label: "14 years" }
];
When I select an option in the select inside the UI, the following error is returned:
age must be a `object` type, but the final value was: `null` (cast from the value `0.6`). If "null" is intended as an empty value be sure to mark the schema as `.nullable()`
How do I make the validation work correctly?
Link to sandbox

You require age to be of type object, but set it the value of the selected option. That is what triggers your wrong validation. Here is how to fix your validation:
If you want to keep age to be an object, change your schema to the following:
const Schema = Yup.object().shape({
age: Yup.object().shape({
label: Yup.string().required("Required"),
value: Yup.string().required("Required")
})
});
else set it to the following:
const Schema = Yup.object().shape({
age: Yup.string()
});
Update your onChange on the Select component to set the value to the option instead of the option.value if you want to use the object in your schema validation.
<Select
{ ... }
value={field.value} // This can be set like this as a result of the change
onChange={option => form.setFieldValue(field.name, option)}
/>
That should get it to work.

Yup Validation Schema
const validationSchema = function (values) {
return Yup.object().shape({
courseId: Yup.string().required("Required").nullable()
})
}
React-select custom class component
import React from 'react'
import Select from 'react-select'
class SingleSelectField extends React.Component {
constructor(props) {
super(props);
}
handleChange = (item) => {
if(item)
this.props.onChange(this.props.type, item.value);
else
this.props.onChange(this.props.type, "");
};
handleBlur = () => {
this.props.onBlur(this.props.type, true);
};
render() {
const defaultValue = (options, value) => {
return options ? options.find(option => option.value === value) : "";
};
return (
<div>
<Select
options={this.props.options}
onChange={this.handleChange}
onBlur={this.handleBlur}
value={defaultValue(this.props.options, this.props.value)}
isClearable
/>
{!!this.props.error && this.props.touched && (
<div style={{ color: "red", marginTop: ".5rem" }}>
{this.props.error}
</div>
)}
</div>
)}
}
export { SingleSelectField as default };

Related

How to generate excel file using write-excel-file library when I need to map through elemenents of data

I need to generate excel report using write-excel-file library.
On simple mock data it works to me but in reality I need to provide data mapping through array of objects.
This is how array of objects looks like:
export const benefits = [
{
comment: "brak",
companyCost: 90,
luxmedType: {
package: "Indywidualny",
type: "Opieka standardowa",
cost: 0
},
persons: [
{
name: "Jan",
lastName: "Kowalski",
cooperationForm: "b2b",
type: "b2b"
}
]
},
{
comment: "nic do skomentowania",
companyCost: 50,
luxmedType: {
package: "Rodzinny",
type: "Opieka rozszerzona",
cost: 100
},
persons: [
{
name: "Tomasz",
lastName: "Michalski",
cooperationForm: "umowa o pracę",
type: "pracownik"
},
{
name: "Ewa",
lastName: "Michalska",
cooperationForm: "null",
type: "partner"
}
]
}
];
Data should be mapped through benefits array and inside persons array. I have tried firstly to map through benefits array but it does not return any values in excel. Can You please suggest how to solve it ?
import "./styles.css";
import writeXlsxFile from "write-excel-file";
import { benefits } from "./benefits";
const HEADER_ROW = [
{
value: "Name and Last Name",
fontWeight: "bold"
},
{
value: "Contract Type",
fontWeight: "bold"
},
{
value: "Employee/Partner/Kid",
fontWeight: "bold"
},
{
value: "Package Type",
fontWeight: "bold"
},
{
value: "Employee Cost",
fontWeight: "bold"
},
{
value: "300B Cost",
fontWeight: "bold"
},
{
value: "Comments",
fontWeight: "bold"
}
];
const DATA_ROW_BENEFITS = benefits.map((element) => {
return [
{
type: String,
value: "józek"
},
{
type: String,
value: "Contract Type"
},
{
type: String,
value: "Employee/Partner/Kid"
},
{
type: String,
value: "Package Type"
},
{
type: Number,
value: 50
},
{
type: Number,
value: 100
},
{
value: "Comment"
}
];
});
const data = [HEADER_ROW, DATA_ROW];
export default function App() {
const createExcelReport = async () => {
console.log("click");
await writeXlsxFile(data, {
fileName: "benefits.xlsx"
});
};
I have also prepared codesandbox so it will be easier to look and check:
https://codesandbox.io/s/thirsty-mccarthy-5b40tj?file=/src/App.js:1359-1573
If You have also other working libraries to export excel files I'm open, I tried few others but they didn't work.
thanks

How to can I do this array desctructure with condition?

I am facing one problem with array destructuring, Please read my questions-
This is array 1-
const Array1 = [
{
label: "Fashion",
value: 1
},
{
label: "Electronics",
value: 2
}
]
This is array2-
const Array2 = [
{
id: 1,
values: [
{ value: "S", meta: "s" },
{ value: "M", meta: "m" },
{ value: "Xl", meta: "xl" },
]
},
{
id: 2,
values: [
{ value: "Red", meta: "red" },
{ value: "Yellow", meta: "yellow" },
{ value: "Green", meta: "green" },
]
}
]
I have to combine this two array when Id(array2) matched to value(array1) and also change field label- like I need actually like this-
const Array3 = [
{
name: "Fashion",
options: [
{ value: "S", label: "s" },
{ value: "M", label: "m" },
{ value: "Xl", label: "xl" },
]
},
{
name: "Electronics",
options: [
{ value: "Red", label: "red" },
{ value: "Yellow", label: "yellow" },
{ value: "Green", label: "green" },
]
}
]
I have already tried in this way-
const Array3 = Array1.map((item) => {
return {
name: item.label,
values: [],
options: Array2.map((e: any) => {
if (e.id === item.value) {
return e.values.map((v: any) => {
return {
label: v.meta,
value: v.value
}
})
}
})
}
})
From this function I am getting - one extra field with undefined-
Please click to see
But it's not working. Please help me by giving a correction of my functions.
You could take an object for the names and map new objects with name instead of id as properties.
const
array1 = [{ label: "Fashion", value: 1 }, { label: "Electronics", value: 2 }],
array2 = [{ id: 1, values: [ { value: "S", meta: "s" }, { value: "M", meta: "m" }, { value: "Xl", meta: "xl" }] }, { id: 2, values: [{ value: "Red", meta: "red" }, { value: "Yellow", meta: "yellow" }, { value: "Green", meta: "green" }] }],
names = Object.fromEntries(array1.map(({ label, value }) => [value, label])),
result = array2.map(({ id, values }) => ({
name: names[id],
options: values.map(({ meta: label, ...o }) => ({ ...o, label }))
}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The reason why Array2.map doesn't work is that map function always returns the same number of elements as the array you run .map on. So adding a condition results in undefined elements when condition doesn't match. You can do this:
options: Array2.find(e => e.id === item.value).values.map(v => {
return { value: v.value, label: v.meta }
})
While this works, I'd recommend taking a look at #Nina Scholz's answer too as it makes use of Object/Dictionary which is much more efficient than running .find on Array2. O(1) vs O(n). So, if you expect to have lots of elements in Array2 or run this quite frequently then the more efficient solution would help
Maybe this is what you want?
const Array3 = Array1.map(array1Item => {
const foundIndex = Array2.findIndex(array2Item => array2Item.id === array1Item.value);
if(foundIndex !== -1) {
return {
name: array1Item.label,
options: Array2[foundIndex].values
}
};
return undefined;
});
console.log(Array3);
/**
[{
name: "Fashion"
options: Array(3)
0: {value: 'S', meta: 's'}
1: {value: 'M', meta: 'm'}
2: {value: 'Xl', meta: 'xl'}
},{
name: "Electronics"
options: Array(3)
0: {value: 'Red', meta: 'red'}
1: {value: 'Yellow', meta: 'yellow'}
2: {value: 'Green', meta: 'green'}
}]
*/

How can I pass two option arrays for the same dropdown component in react js?

I want to reuse my dropdown component in two other containers by passing two different set of array.
Example: One dropdown menu used for creating a breakfast option menu and the other for dinner option menu.
I've tried creating two arrays in state part of the class. but when i call the array it shows no options
class DropDownDropper extends React.Component {
constructor(props){
super(props);
this.state = {options: [
breakfast: [
{ label: "Location 1", value: 1 },
{ label: "Location 2", value: 2 },
{ label: "Location 3", value: 3 },
{ label: "Location 4", value: 4 },
{ label: "Location 5", value: 5 },
{ label: "Location 6", value: 6 },
] ,
dinner: [
{ labe: "Venue 1", value: 1 },
{ labe: "Venue 2", value: 1 },
{ labe: "Venue 3", value: 1 },
{ labe: "Venue 4", value: 1 },
{ labe: "Venue 5", value: 1 },
]
]
}
}
render() {
return(
<div className="container">
<div className="row">
<div className="col-md-4"></div>
<div className="col-md-4">
<Select options = { this.props.options
} />
</div>
<div className="col-md-4"></div>
</div>
</div>
);
}
}
here my solution for you :
App.js
import React from "react";
import ReactDOM from "react-dom";
import Dropdown from "./Dropdown";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
categories: [
{
name: "Breakfast",
options: [
{ label: "Location 1", value: 1 },
{ label: "Location 2", value: 2 },
{ label: "Location 3", value: 3 },
{ label: "Location 4", value: 4 },
{ label: "Location 5", value: 5 },
{ label: "Location 6", value: 6 }
]
},
{
name: "dinner",
options: [
{ label: "Venue 1", value: 1 },
{ label: "Venue 2", value: 1 },
{ label: "Venue 3", value: 1 },
{ label: "Venue 4", value: 1 },
{ label: "Venue 5", value: 1 }
]
}
]
};
}
render() {
return (
<div className="App">
{this.state.categories.map(cat => (
<Dropdown options={cat.options} title={cat.name} />
))}
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
i have recreated your state because you cannot create a array inside array ! you need object for that.
so state has a categories array which has name and options.
Dropdown.js
import React from "react";
const Dropdowm = ({ options, title }) => (
<div>
<h1>{title}</h1>
<select>
{options.map(option => (
<option value={option.value}>{option.label}</option>
))}
</select>
</div>
);
export default Dropdowm;
codesandbox
You need something to return in render method. The best option would be to create a component that returns a dropdown list and pass the dropdowns data from the class in which you want to display the dropdown.

Nativescript RadDataForm convert property not working with json array

I have json array with id and name property after submiting the form I am getting NaN I want to convert converter code from typescript to Javascript
I created a Playground example
file : Data.js
{ id: 123, name: 'Zootopia' },
{ id: 217, name: 'Captain America' },
{ id: 324, name: 'The Jungle Book' }
];
export class MovieConverter {
constructor(_movies) {
this._movies = _movies;
}
convertFrom(id) {
return this._movies.filter((movie) => movie.id === id)[0].name;
}
convertTo(name) {
return this._movies.filter((movie) => movie.name === name)[0].id;
}
}
Test.vue
<template>
<Page>
<ActionBar :title="Simple">
<NavigationButton text="Back" android.systemIcon="ic_menu_back"
#tap="onNavigationButtonTap"></NavigationButton>
</ActionBar>
<StackLayout>
<Button text="Button" #tap="onSubmit" />
<RadDataForm :source="ticket" :metadata="ticketMetadata" />
</StackLayout>
</Page>
</template>
<script>
import Home from "./Home";
import {
Movies,
MovieConverter
} from "../Data";
//const movies = getMovies();
export default {
methods: {
onNavigationButtonTap() {
this.$navigateTo(Home);
}
},
mounted() {
console.log("movies", Movies);
Movies.map(Movie => {
console.log(Movie.name);
});
},
data() {
return {
ticket: {
movie: 123,
date: "2016-04-06",
time: "20:00",
type: "2D",
price: 9.5,
numberOfTickets: 2,
contactName: null,
contactPhone: null,
contactEmail: null,
agreeTerms: false
},
ticketMetadata: {
isReadOnly: false,
commitMode: "Immediate",
validationMode: "Immediate",
propertyAnnotations: [{
name: "movie",
displayName: "Movie Name",
index: 0,
editor: "Picker",
valuesProvider: Movies.map(Movie => Movie.name),
converter: new MovieConverter(Movies)
},
{
name: "date",
displayName: "Date",
index: 1,
editor: "DatePicker",
hintText: "This is a hint for you"
},
{
name: "time",
displayName: "Time",
index: 2,
editor: "TimePicker"
},
{
name: "type",
displayName: "Type",
index: 3,
editor: "SegmentedEditor",
valuesProvider: ["2D", "3D"]
},
{
name: "price",
displayName: "Price",
index: 4,
editor: "Decimal",
readOnly: true
},
{
name: "numberOfTickets",
displayName: "Number Of Tickets",
index: 5,
editor: "Stepper",
editorParams: {
minimum: 0,
maximum: 20,
step: 2
}
},
{
name: "contactName",
displayName: "Contact Name",
index: 6,
editor: "Text"
},
{
name: "contactPhone",
displayName: "Contact Phone",
index: 7,
editor: "Phone"
},
{
name: "contactEmail",
displayName: "Contact Email",
index: 8,
editor: "Email"
},
{
name: "agreeTerms",
displayName: "I Agree with Terms",
index: 9,
editor: "Switch"
}
]
}
};
},
methods: {
onSubmit() {
console.log("submit", this.ticket.movie);
}
}
};
</script>
<style>
</style>
after onSubmit() of the form I should get 'submit' 123
but getting 'submit' NaN.
I check out the documentation of converter it showing a class with 2 methods convertFrom() & convertTo() which I think is proper not understand as all the documentation is in typescript and I am using javascript.

add and remove item in array on click reactjs

I'm working on a simple table using reactjs and ant design.
My plan is to add and remove a new item on the list on button click.
My problem is I don't know how to do that.
I tried to follow this thread but no luck.
Hope you understand me.
Thanks.
sample code
function remove() {
console.log("remove");
}
function add() {
console.log("add");
}
const columns = [
{
title: "Num",
dataIndex: "num"
},
{
title: "Name",
dataIndex: "name"
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
const data = [
{
key: "1",
num: 1,
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park"
},
{
key: "2",
num: 2,
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park"
},
{
key: "3",
num: 3,
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park"
}
];
<Table pagination={false} columns={columns} dataSource={data} />
<Button type="primary" onClick={add}>
add
</Button>
<Button type="danger" onClick={remove}>
remove
</Button>
You need to use react state. State holds the data, when you want to add or remove, update this state and react with re-render the table.
I have updated your code. On click of add a new random row is added. On click of remove last row is removed.
CodeSandbox
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Table, Button } from "antd";
function remove() {
console.log("remove");
}
const columns = [
{
title: "Num",
dataIndex: "num"
},
{
title: "Name",
dataIndex: "name"
},
{
title: "Age",
dataIndex: "age"
},
{
title: "Address",
dataIndex: "address"
}
];
let data = [
{
key: "1",
num: 1,
name: "John Brown",
age: 32,
address: "New York No. 1 Lake Park"
},
{
key: "2",
num: 2,
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park"
},
{
key: "3",
num: 3,
name: "Joe Black",
age: 32,
address: "Sidney No. 1 Lake Park"
}
];
export default class MyTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data
};
}
add = () => {
var row = {
key: "99",
num: 99,
name: "I am New",
age: 32,
address: "New Address"
};
var newStateArray = [...this.state.data];
newStateArray.push(row);
this.setState(() => {
return {
data: newStateArray
};
});
}
remove = () => {
var newStateArray = [...this.state.data];
if(newStateArray.length > 1) {
newStateArray.pop();
}
this.setState(() => {
return {
data: newStateArray
};
});
}
render() {
return (
<div>
<Table
pagination={false}
columns={columns}
dataSource={this.state.data}
/>
<Button type="primary" onClick={this.add}>
add
</Button>
<Button type="danger" onClick={this.remove}>
remove
</Button>
</div>
);
}
}
ReactDOM.render(<MyTable />, document.getElementById("container"));

Categories

Resources