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>
)
}
Related
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
Thanks for your time reading.
I need to sort countries by Language or Continent, the user selects the option he wants in the buttons.
countries is an array of objects of each country that contain:
languages is an array of objects, because each country can have more than one language
continent is an object with the continent name
Complete example(countries array content): https://github.com/gonzaloramosf/countries
If the user select for example continents and types in the input "es" all the results related whit content Asia listed together in a group and do not repeat the continent title in each one, same i need with languages.
This is my code:
const CountrySearch = ({countries}) => {
const [searchTerm, setSearchTerm] = useState("");
console.log(countries)
return (
<div className="search">
<h1>Country Search</h1>
<span> Some random text </span>
<div className="searchResults">
<input type="text" placeholder="Search country..." onChange={event => {
setSearchTerm(event.target.value)
}}/>
<div className="groupBy">
<h2> Group by: </h2>
<div>
<button> Continent </button>
<button> Language </button>
</div>
</div>
<div>
{countries.filter((val) => {
if (searchTerm === "") {
return ""
} else if (val.name.toLowerCase().includes(searchTerm.toLowerCase())){
return val
}
}).map((val, key) => {
return (
<div key={key}>
<h2> {val.continent.name} </h2>
<div className="countryInfo">
<div>
<span>{val.emoji}</span>
<h3> {val.name} </h3>
</div>
<p> Capital: {val.capital} </p>
<p> Currency: {val.currency} </p>
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
export default CountrySearch;
First filter the data and then group it by continent using reduce and then loop over the arrays and create the desired JSX.
You can refer the snippet below (type "s" in the input box):
const countries = [
{
name: "India",
continent: { name: "Asia" },
languages: [{ name: "Hindi" }, { name: "English" }, { name: "Marathi" }],
},
{
name: "Sri Lanka",
continent: { name: "Asia" },
languages: [{ name: "Srilankan" }, { name: "Tamil" }],
},
{
name: "Spain",
continent: { name: "Europe" },
languages: [{ name: "Spanish" }, { name: "English" }],
},
{
name: "Slovakia",
continent: { name: "Europe" },
languages: [{ name: "English" }],
},
];
function App() {
const [searchTerm, setSearchTerm] = React.useState("");
return (
<div>
<input
type="text"
value={searchTerm}
onChange={({ target }) => setSearchTerm(target.value)}
/>
{Object.entries(
countries
.filter((c) =>
c.name.toLowerCase().includes(searchTerm.toLowerCase())
)
.reduce((res, c) => {
if (!res[c.continent.name]) {
res[c.continent.name] = [];
}
res[c.continent.name].push(c);
return res;
}, {})
).map(([continent, countries]) => (
<ul key={continent}>
<li>
<div>{continent}</div>
<ul>
{countries.map(({ name, languages }) => (
<li key={name}>
<div>{name}</div>
<ul>
{languages.map(({ name }) => (
<li key={name}>{name}</li>
))}
</ul>
</li>
))}
</ul>
</li>
</ul>
))}
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
Following is the portion of code from the above snippet that does the grouping:
Object.entries(
countries
.filter((c) => c.name.toLowerCase().includes(searchTerm.toLowerCase()))
.reduce((res, c) => {
if (!res[c.continent.name]) {
res[c.continent.name] = [];
}
res[c.continent.name].push(c);
return res;
}, {})
);
I have a data as below
myArr = [
{
"Sl.No": "n1",
company: "ABC",
Name: "Sam",
Designation: "Architect",
Salary: "100",
},
{
"Sl.No": "n2",
company: "ABC",
Name: "Bill",
Designation: "Engineer",
Salary: "200",
},
{
"Sl.No": "n3",
company: "ABC",
Name: "Jill",
Designation: "HR",
Salary: "300",
},
{
"Sl.No": "n4",
company: "XYZ",
Name: "Bill",
Designation: "Engineer",
Salary: "250",
},
{
"Sl.No": "n5",
company: "XYZ",
Name: "Tom",
Designation: "Mechanic",
Salary: "150",
},
{
"Sl.No": "n6",
company: "LMN",
Name: "Tom",
Designation: "Mechanic",
Salary: "150",
},
];
I want to create a react app which shows the data as below. Nothing but listing the employees & their designations under the name of the company.
The boxes on the right are number of doses of vaccine taken (data comes from somewhere else)
I have the components set-up like so
Inside App.js
I have left out importing the components , css & all that for simplcity
export const App = () => {
return (
<div className=app}>
<CompanyInfo />
</div>
);
}
Inside CompanyInfo.js
export const CompanyInfo= () => {
let companies= [...new Set(myArr.map((item) => item.company))];
const renderingComponents = (company: string, index: number) => {
return (
<Fragment key={index}>
<p className="company-name">{company}</p>
<div className="category-employees">
<CompanyEmployee toggled={toggled} />
</div>
;
</Fragment>
);
};
return (
<div className=company-info}>
{companies.map((item, index) => renderingComponents(item, index))}
</div>
);
}
So far, so good, I can render the titles of all the companies & I have hardcoded the CompanyEmployee to see if it populates within every company & it does. However, I want CompanyEmployee to be dynamic & I am not able to figure our how to pass the company related info to the components (that data will have info of all the company employees) and then map the CompanyEmployee component on that data.
Inside CompanyEmployee.js
Please note that this is like a wrapper for 2 components
export const CompanyEmployee= () => {
return (
<div className=ce}>
<EmployeePrimaryDetails />
<EmployeeVacDetails />
</div>
);
}
Inside EmployeePrimaryDetails.js
export const EmployeePrimaryDetails= (props) => {
return (
<div className=epd>
<span className="name">{props.Name}</span>
<span className="designation">{props.Designation}</span>
</div>
);
}
Can anyone guide me on how I render EmployeePrimaryDetails.js for each employee of the company?
I tried to do a for of, forEach, map in the renderingComponents function of CompanyInfo itself but when I try that I get the Typescript error "Expression Expected" (I am using typescript with React in my project).
Any help is appreciated.
Inside renderingComponents function you can say:
const filteredList = myArr.filter(employee => employee.company === company);
filteredList.map(employee => (<CompanyEmployee employee={employee} toggled={toggled} />));
Resulting in this:
const renderingComponents = (company: string, index: number) => {
const filteredList = myArr.filter(employee => employee.company === company);
return (
<Fragment key={index}>
<p className="company-name">{company}</p>
<div className="category-employees">
{filteredList.map(employee => (<CompanyEmployee employee={employee} toggled={toggled} />))}
</div>
;
</Fragment>
);
};
So in CompanyEmployee component you can destructure the data you need.
I have some json data that looks like this:
{
"author1": {
"books": [{
"title": "title1"
},
{
"title": "title2"
}
],
"movies": [{
"title": "movie1"
},
{
"title": "movie2"
}
]
}
}
I want to have a dropdown that is populated with the authors (like author1). When that dropdown is selected, I then want to populate another dropdown with the keys for that author (books and movies).
So. if my json is in a variable named data, I want to populate the first dropdown with data.keys() and then the second with data[author].keys() where author is the value selected in the first dropdown.
I have seen a lot of examples of populating with json, but not with the keys. I am very new to React JS, so I am not sure how to do this.
Thanks
you can use the following flow.
const AuthorData = ({ symtomsData }) => {
const [data] = useState({
author1: {
books: [
{
title: "title1"
},
{
title: "title2"
}
],
movies: [
{
title: "movie1"
},
{
title: "movie2"
}
]
},
author2: {
books: [
{
title: "titleasd"
},
{
title: "titleqweqw"
}
],
movies: [
{
title: "movieqwewq"
},
{
title: "movieqweqwe"
}
]
}
});
const [authorWroks, setAuthorWorks] = useState({});
const [selectedData, setSelectedData] = useState([]);
const onChangeAuthor = useCallback(
(e) => {
const authorSelected = e.target.value;
if (authorSelected) {
setAuthorWorks(data[authorSelected]);
} else {
setAuthorWorks([]);
}
},
[data]
);
const changeItem = useCallback(
(e) => {
const itemSelected = e.target.value;
console.log("itemSelected", itemSelected);
if (itemSelected) {
console.log("authorWroks", authorWroks[itemSelected]);
setSelectedData(authorWroks[itemSelected]);
} else {
setSelectedData([]);
}
},
[authorWroks]
);
return (
<Grid>
<Col>
<select onChange={onChangeAuthor}>
<option value="">select</option>
{Object.keys(data).map((key, i) => (
<option value={key}>{key}</option>
))}
</select>
<br />
<div>
{Object.keys(authorWroks).length ? (
<select onChange={changeItem}>
<option value="">select</option>
{Object.keys(authorWroks || []).map((key, i) => (
<option onChange={changeItem} value={key}>
{key}
</option>
))}
</select>
) : undefined}
</div>
<div>
{selectedData.length ? (
<select>
<option value="">select</option>
{(selectedData || []).map((data, i) => (
<option onChange={changeItem} value={data.title}>
{data.title}
</option>
))}
</select>
) : undefined}
</div>
</Col>
</Grid>
);
};
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