Get key from datalist in Form.Control - javascript

I get from the API a json, with a list of each staff member:
const MOCK_STAFF = [{
id: 1,
name: "Jhon Doe",
department: "HR"
}, {
id: 2,
name: "Jane Doe",
department: "Research"
}, etc
Then they get mapped in a datalist <option>, inside a Form.Control component:
<Form.Group className="mb-3">
<Form.Label>Name</Form.Label>
<Form.Control
name='staffName'
value={0}
list="namesList"
onChange={(e) => onChangeHandler(e)}/>
<Form.Label>Department</Form.Label>
<Form.Control disabled
name=department
value={}
/>
<datalist id="namesList">
{MOCK_DATA.map( (data) => (
<option key={data.id} value={data.department}>{data.name}</option>
))}
</datalist>
</Form.Group>
sandbox link: https://codesandbox.io/s/modal-t59e7z?file=/src/App.tsx
I would like the onChange={(e) => onChangeHandler(e)} to get the data-value of <option key={data.id} on form submit, and to make the department Form.Control to reference the value of <option value={data.department} in the datalist. The 'key' id must not show to the user, it is used as a primary key on the database.
I have tried:
function onChangeHandler(e:React.SyntheticEvent) {
console.log(e.target.key);
}
but "property key does not exist on event.target". Nor I can use document.getElementById(); with react. How can I get the values 'key', 'value' and/or 'default-value` from a Form.Control with a datalist?
Thank you

I could not achieve this with data-list, but did so with react-select:
type StaffOption = {
label: string, value: number
}
const MOCK_DATA= [{
id: 1,
name: "Jhon Doe",
department: "HR"
}, {
id: 2,
name: "Jane Doe",
department: "Research"
}, {
id: 3,
name: "Elizabeth meyer",
department: "Operations"
}]
type NameOption = {value: number, label: string, department: string}
type NameOptions = Array<NameOption>
function AddNewRowModal(props:AddNewRowProps) {
const [selectedStaffID, setSelectedStaffID] = useState(0);
function onChangeHandler(option: OnChangeValue<StaffOption, false>,
actionMeta: ActionMeta<StaffOption>) {
console.log(option); //this returns all 3 values defined on type StaffOption
if (option?.value !== undefined) {
setSelectedStaffID(option.value!);
}
}
function BuildOptions (data:any[]) {
var options:NameOptions = []
data.forEach(element => {
options.push({
value: element.id!,
label: (element.name),
department: element.department});
});
return options;
var nameOptions = BuildOptions(MOCK_DATA);
return (
<Modal
show={props.showModal}
backdrop="static"
keyboard={false}
onHide={() => props.closeModal()} >
<Modal.Header closeButton>
<Modal.Title>Add new Entry</Modal.Title>
</Modal.Header>
<Modal.Body>
<Select
options={nameOptions}
onChange={onChangeHandler} />
</Modal.Body>
<ModalFooter>
<Button variant='primary'>Create Entry</Button>
<Button variant='danger' onClick={() => props.closeModal()}>Cancel</Button>
</ModalFooter>
</Modal>
);
}
And the codesandbox

Related

Compare Two values in Javascript then map

I have Different data from different Schema. I want to compare data from both schema to find out if they are same then map through them.
allStaff = [
{
role: "admin",
name: "jamal",
branch: {
name: "kansas",
},
},
{
role: "legal",
name: "keith",
branch: {
name: "arizona",
},
},
{
role: "admin",
name: "dwyane",
branch: {
name: "kansas",
},
},
...
];
contributor = {
fullName: 'Aaraf',
branch: {
name: "kansas",
},
};
I want the form option for this contributor to contain only staff in same branch as he is(kansas).
<Form.Group controlId="branch">
<Form.Label>Investment Officer:</Form.Label>
<Form.Control
as="select"
value={investmentOfficer}
onChange={(e) => setInvestmentOfficer(e.target.value)}
>
<option>Investment Officer</option>
{(allStaff.branch?.name === contributor.branch?.name).map(
staff) => (
<option key={staff._id} value={staff._id}>
{staff.name}
</option>
)
)}
</Form.Control>
</Form.Group>
You want to filter your data first before you map it so that allStaff is filtered down where the branch names match. I have provided the code to do so below.
allStaff.filter((staff) => staff.branch.name === contributor.branch.name)
.map((filteredStaff) => (
<option key={staff._id} value={staff._id}>
{staff.name}
</option>
));
the problem is your code trying to map a boolean... .map() is built in attribute for array, also your code not explaining what is you trying to iterate, so the first step figure out which array you want to iterate first, after that follow this example,
for this example, im going to assume that you want to iterate allStaff.branch
{allStaff.branch.map(
(branch,i) => {
if(branch?.name === contributor.branch[i].name)
return (<option key={branch._id} value={branch._id}>
{branch.firstName}
</option>)
})}

Material UI react autocomplete set different label and different value

we can see the example here at https://material-ui.com/components/autocomplete/
I wanted to set option label and value different.
Like here is example used
const defaultProps = {
options: top5Films,
getOptionLabel: (option) => option.title,
};
<Autocomplete
{...defaultProps}
id="auto-complete"
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
autoComplete
includeInputInList
renderInput={(params) => <TextField {...params} label="clearOnEscape" margin="normal"/>}
/>
const top5Films= [
{ title: 'The Shawshank Redemption', year: 1994 },
{ title: 'The Godfather', year: 1972 },
{ title: 'The Godfather: Part II', year: 1974 },
{ title: 'The Dark Knight', year: 2008 },
{ title: '12 Angry Men', year: 1957 }
]
But I have data like:
const top5Films= [
{ id: 1, title: 'The Shawshank Redemption', year: 1994 },
{ id: 2, title: 'The Godfather', year: 1972 },
{ id: 3, title: 'The Godfather: Part II', year: 1974 },
{ id: 4, title: 'The Dark Knight', year: 2008 },
{ id: 5, title: '12 Angry Men', year: 1957 }
]
I want to set id as value and show title as label.
Looks like the object is assigned to the value.
So setting id to value crashed the options.
I used the id from the object in following way for further operation.
/* eslint-disable no-use-before-define */
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function Playground() {
const [value, setValue] = React.useState(null);
const [id, setId] = React.useState(null);
const [title, setTitle] = React.useState(null);
return (
<div>
<div>{`value: ${value}`}</div>
<div>{`id: ${id}`}</div>
<div>{`title: ${title}`}</div>
<br />
<div style={{ width: 300 }}>
<Autocomplete
options={top5Films}
getOptionLabel={option => option.title}
id="movies"
value={value}
onChange={(event, newValue) => {
console.log(newValue);
if (newValue) {
setValue(newValue);
setId(newValue.id);
setTitle(newValue.title);
}
}}
renderInput={params => (
<TextField {...params} label="Movies" margin="normal" />
)}
/>
</div>
</div>
);
}
// Top 5 films as rated by IMDb users. http://www.imdb.com/chart/top
const top5Films = [
{ id: 1, title: "The Shawshank Redemption", year: 1994 },
{ id: 2, title: "The Godfather", year: 1972 },
{ id: 3, title: "The Godfather: Part II", year: 1974 },
{ id: 4, title: "The Dark Knight", year: 2008 },
{ id: 5, title: "12 Angry Men", year: 1957 }
];
This works for now but best answer is always welcome.
I couldn't fully understand your issue but I guess you want to have different display option for getOptionLabel and different display in the dropdown list. If that's the case, you can simply use renderOption provided by the Material UI. Check out an example here : https://codesandbox.io/s/94xlp?file=/demo.js
import React from "react";
import { TextField } from "#material-ui/core";
import { Autocomplete } from "#material-ui/lab";
return (
<Autocomplete
freeSolo
name=""
options={top5Films }
getOptionLabel={(option) => option.title} //Displays title only in input field
renderOption={(option) => (
<React.Fragment>
{option.title + "," + " " + option.year} //Displays title + year in dropdown list
</React.Fragment>
)}
renderInput={params => (
<Field
component={FormikTextField}
{...params}
label=""
name=""
/>
)}
/>
);
}
Try this, set a json value and use the name for inputValue, the input value not change why not calling onChange function
<Autocomplete
options={machineList}
inputValue={formValues.machine.name || ""} // from formik context in my case
onChange={(e, value) => {
setValues(
"machine",
value && value.id ? { id: value.id, name: value.name } : "" //use json value
);
}}
getOptionLabel={(option) => option.name}
renderInput={(params) => (
<InputField
{...params}
type="text"
name={machine.name}
label={machine.label}
fullWidth
/>
)}
/>;
okk you can try in this way -
const options = [
{
id: 212,
label: 'First Title'
},
{
id: 321,
label: 'Second Title'
},
{
id: 543,
label: 'Third Title'
}
]
<Autocomplete
placeholder="Search…"
options={options}
isOptionEqualToValue={(option, value) => option.id === value.id}
renderInput={(params) => <TextField {...params} label={'label'} />}
renderOption={(props, option) => {
return (
<li {...props}>
{option.label}
</li>
)
}}
onChange={(_event, value) => {
setValue(value.id)
}}
/>

React pass array of objects to component which are using different keys

I'm trying to render a dynamic <select> element generated by React: the input-select component (see below), takes an array of objects and every object must contain the value for the <option> and its caption.
export default function InputSelect (props) {
return (
<label className="form__element">
{props.caption}
<select className="input-select">
{props.items.map((item, i) => <option key={i} value={item.value}>{item.caption}</option>)}
</select>
</label>
)
}
This becomes a problem when I try to pass an array of objects like this one:
[
{code: "IT", name: "Italy"},
{code: "US", name: "United States"},
]
where I have different keys which i cannot change since this data is retrieved from db.
How can i fix this?
Use Array.prototype.map()
<InputSelect
items={
[
{code: "IT", name: "Italy"},
{code: "US", name: "United States"},
].map(country => ({value: country.code, caption: country.name}))
}
/>
Assuming you own the InputSelect component, you could have it take the caption and value keys from props, like this:
const { Fragment } = React
const countries = [
{code: "IT", name: "Italy"},
{code: "US", name: "United States"},
]
const languages = [
{value: 'IT', caption: 'Italiano'},
{value: 'DE', caption: 'Tedesco'}
]
const App = props => (
<Fragment>
<InputSelect caption="Country" items={countries} captionKey="name" valueKey="code" />
<br />
<InputSelect caption="Language" items={languages} />
</Fragment>
)
function InputSelect ({ valueKey = 'value', captionKey = 'caption', ...props }) {
return (
<label className="form__element">
{props.caption}
<select className="input-select">
{props.items.map((item) => <option key={item[valueKey]} value={item[valueKey]}>{item[captionKey]}</option>)}
</select>
</label>
)
}
ReactDOM.render(<App />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is one of only several options, which also include mapping over items to create a new array with the correct property keys, which could look like this:
const { Fragment } = React
const countries = [
{code: "IT", name: "Italy"},
{code: "US", name: "United States"},
]
const languages = [
{value: 'IT', caption: 'Italiano'},
{value: 'DE', caption: 'Tedesco'}
]
const App = props => (
<Fragment>
<InputSelect
caption="Country"
items={countries.map(
({ code, name }) => ({ value: code, caption: name })
)}
/>
<br />
<InputSelect caption="Language" items={languages} />
</Fragment>
)
function InputSelect (props) {
return (
<label className="form__element">
{props.caption}
<select className="input-select">
{props.items.map((item) => <option key={item.value} value={item.value}>{item.caption}</option>)}
</select>
</label>
)
}
ReactDOM.render(<App />, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You can pass a mapper object to your Select:
function App () {
const apiData = [
{code: "IT", name: "Italy"},
{code: "US", name: "United States"},
]
const mapper = {value: 'code', caption: 'name'};
return(
<InputSelect items={apiData} mapper={mapper} />
)
}
const defaultMapper = {value: "value", caption: "label"}
function InputSelect ({ items, caption, mapper = defaultMapper }) {
const {value, caption} = mapper
return (
<label className="form__element">
{caption}
<select className="input-select">
{items.map((item, i) => <option key={i} value={item[value]}>{item[caption]}</option>)}
</select>
</label>
)
}

show form in dynamic column when iterating

I have a long form which has mixed type of column. I mean some field will be of width 100% some field will be around 33% so that i can show 3 fields in a column and so on. This I could do without iterating, but how can it be done if the fields are shown with iteration to avoid code repetition? I have created a sandbox either and I am not using third party css framework like bootstrap just a flex or may be grid would work.
Here is how i have tried:
const formFields = [
{ id: 1, name: "first_name", component: TextField, label: "First Name" },
{ id: 1, name: "last_name", component: TextField, label: "Last Name" },
{ id: 1, name: "age", component: TextField, label: "Age" },
{ id: 1, name: "city", component: TextField, label: "City" },
{ id: 1, name: "state", component: TextField, label: "State" },
{ id: 1, name: "country", component: TextField, label: "Country" }
];
const FlexibleForm = () => {
return (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
<FlexRow>
<FlexColumn size={12}>
{formFields.map(({ id, name, label, component }) => {
return (
<Field
key={id}
name={name}
label={label}
component={component}
/>
);
})}
</FlexColumn>
</FlexRow>
<h2>Another without iteration</h2>
<FlexRow>
<FlexColumn size={6}>
<Field name="first_name" label="First Name" component={TextField} />
</FlexColumn>
<FlexColumn size={6}>
<Field name="last_name" label="Last Name" component={TextField} />
</FlexColumn>
</FlexRow>
<FlexRow>
<FlexColumn size={12}>
<Field name="age" label="Age" component={TextField} />
</FlexColumn>
</FlexRow>
<FlexRow>
<FlexColumn size={4}>
<Field name="city" label="City" component={TextField} />
</FlexColumn>
<FlexColumn size={4}>
<Field name="state" label="State" component={TextField} />
</FlexColumn>
<FlexColumn size={4}>
<Field name="country" label="Country" component={TextField} />
</FlexColumn>
</FlexRow>
</Form>
</React.Fragment>
);
};
export default reduxForm({
form: "form"
})(FlexibleForm);
export const FlexRow = styled.div`
display: flex;
div {
margin-right: 10px;
}
`;
export const FlexColumn = styled.div`
width: ${props => (props.size / 12) * 100}vw;
`;
Here is the codesandbox link https://codesandbox.io/s/l7lm1qp0pq
This is <FlexRow> component
const FlexRow = props => {
return (
<div style={{ display: "flex" }}>
{props.elements.map((element, index) => {
const obj = element.component;
return (
<FlexColumn key={index} size={Math.round(12 / props.elements.length)}>
<Field
name={element.name}
label={element.label}
component={obj[Object.keys(obj)[0]]}
/>
</FlexColumn>
);
})}
</div>
);
};
This is <FlexColumn> component
const FlexColumn = styled.div`
width: ${props => (props.size / 12) * 100}vw;
margin-right: 10px;
`;
Your <FlexibleForm> should look like this :
const FlexibleForm = () => {
return (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
<FlexRow
elements={[
{ name: "first_name", label: "First Name", component: {TextField} },
{ name: "last_name", label: "Last Name", component: {TextField} }
]}
/>
<FlexRow
elements={[{ name: "age", label: "Age", component: {TextField} }]}
/>
<FlexRow
elements={[
{ name: "city", label: "City", component: {TextField} },
{ name: "state", label: "State", component: {TextField} },
{ name: "country", label: "Country", component: {TextField} }
]}
/>
</Form>
</React.Fragment>
);
};
The component <FlexRow> will loop inside his props elements and will create a <FlexColumn> with the good size with the good attributes.
Litteraly, it did loop inside the props elements which is an array and return the size (12 divided by the number of rows you have), you can change this value if you want with a simple condition like :
element.size ? element.size : Math.round(12 / props.elements.length)
and add a size element in your elements array
{ name: "first_name", label: "First Name", size: 6, component: {TextField} }
Then it add the label and the name. And finally it add the component you choosed, in this example it's <TextField>
This will avoid you from repeating your code.
Here's the CodeSandBox to show you the example !
Wish I did helped you.
There is no much point of using flex if you are not using flex-wrap.
If you want to do it with simple float:
const formFields = [
{ id: 1, name: "first_name", component: TextField, label: "First Name",size: 6 },
{ id: 1, name: "last_name", component: TextField, label: "Last Name", size: 6 },
{ id: 1, name: "age", component: TextField, label: "Age", size: 12 },
{ id: 1, name: "city", component: TextField, label: "City", size: 4 },
{ id: 1, name: "state", component: TextField, label: "State", size: 4 },
{ id: 1, name: "country", component: TextField, label: "Country", size: 4 }
];
const Form = styled.form`
width: 100%;
`;
const FlexColumn = styled.div`
float: left;
width: ${props => (props.size / 12) * 100}%;
`;
const FlexibleForm = () => (
<React.Fragment>
<Form onSubmit={() => console.log("something")}>
{formFields.map(({ id, name, label, component, size }) => {
return (
<FlexColumn size={size}>
<Field key={id} name={name} label={label} component={component} />
</FlexColumn>
);
})}
</Form>
</React.Fragment>
);

return array of objects from props React JS

so I have the following component that is a dropdown list created using react-select.
import React from 'react'
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' }
];
class MealsFilters extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedOption: null,
};
}
handleChange = (selectedOption) => {
this.setState({ selectedOption });
console.log(`Option selected:`, selectedOption);
}
render() {
const { selectedOption } = this.state;
return (
<div className="container my-3">
<div className="row">
<div className="col-lg-4 col-md-6 col-sm-8">
<Select
isMulti
isSearchable
placeholder={"catégories"}
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
</div>
</div>
</div>
)
}
}
export default MealsFilters;
the options variable is the default one from the docs. I actually need to replace its values by each meal category available.
To do so, as you can see, I need to create an array of objects with a value and a label.
this component accesses meal categories through props called meals that are like so:
console.log(this.props.meals);
=> [{
id: 0,
name: spaghettis,
category: italian,
price: 5.99},
{
id: 1,
name: hamburger,
category: american,
price: 7.99},
{
etc.
}, {}]
How can I take advantage of this.props.meals to get my options array of objects ?
EDIT: multiple meals can have the same category, and I need each category to only appear once in the options.
Map over your this.props.meals array, and create the needed options array,
<Select
isMulti
isSearchable
placeholder={"catégories"}
value={selectedOption}
onChange={this.handleChange}
options={this.props.meal.map(item=>({value: item.id, label: item.name}))}
/>
You could do something like this:
options={this.props.meals.map(
({id, name})=>({value:id,label:name})
)}
You could also use redux connect to create a container that will map the data to dropdown values for you
You can merge the data by category in the following way:
var items = [
{
id: 0,
name: 'spaghettis',
category: 'italian',
price: 5.99,
},
{
id: 1,
name: 'hamburger',
category: 'american',
price: 7.99,
},
{
id: 2,
name: 'other hamburger',
category: 'american',
price: 7.99,
},
];
console.log(
[
...items.reduce(
(result, item) => (
result.get(item.category)
? result.get(item.category).push(item.id)
: result.set(item.category, [item.id]),
result
),
new Map(),
),
].map(([label, value]) => ({ label, value })),
);
In the component it'll look like this:
options={[
...this.props.meals.reduce(
(result, item) => (
result.get(item.category)
? result.get(item.category).push(item.id)
: result.set(item.category, [item.id]),
result
),
new Map(),
),
].map(([label, value]) => ({ label, value }))}
You only need the "name" property so when you map through meals, simply retrieve it. Then upper case the first letter.
const meals = [{
id: 0,
name: "spaghettis",
category: "italian",
price: 5.99
},
{
id: 1,
name: "hamburger",
category: "american",
price: 7.99
}
]
const result = meals.map(({name}) => ({
label: `${name[0].toUpperCase()}${name.slice(1)}`,
value: name
}))
console.log(result);
You can use getOptionLabel and getOptionValue props.
<Select
options={this.props.meals},
getOptionLabel={m => m.name}
getOptionValue={m => m.id} />
https://react-select.com/props
getOptionLabel generic = (option) => string
Resolves option data to a string to be displayed as the label by components
getOptionValue generic = (option) => string
Resolves option data to a string to compare options and specify value attributes

Categories

Resources