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;
}, {})
);
Related
I'm doing my PET project with Redux toolkit, but have some troubles with inputs. When i add an input on click it adds correctly, but i'm not sure it adds in right place(it's should be added in ITEMS array). Other issue is with it's data. If i add 2 inputs: 1) Item Name: 'Iphone 13', Unit Costs: '1200', unit: '2'; 2) Item Name: 'Iphone 12', Unit Costs: '1100', unit: '1'. It gonna add only last one and array length would be only 1.
Why is it hapenning? What i did wrong?
I've already tried using spread operator, but it shows error when i click on Add Item button.
Now the code.
InvoicesList.js file with INVOICES_LIST array which includes ITEMS where should be all items from inputs.
export const INVOICES_LIST = [
{
id: Math.random().toString(),
number: Math.random().toFixed(2),
invoice_num: "#1232",
bill_from: "Pineapple Inc.",
bill_to: "REDQ Inc.",
total_cost: "14630",
status: "Pending",
order_date: "February 17th 2018",
bill_from_email: "pineapple#company.com",
bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
bill_from_phone: "+(402) 748-3970",
bill_from_fax: "",
bill_to_email: "redq#company.com",
bill_to_address: "405 Mulberry Rd, Mc Grady, NC, 28649",
bill_to_phone: "+(740) 927-9284",
bill_to_fax: "+0(863) 228-7064",
ITEMS: [
{
item_name: "A box of happiness",
unit_costs: "200",
unit: "14",
price: "2800",
sub_total: "133300",
vat: "13300",
grand_total: "14630",
},
],
},
{
id: Math.random().toString(),
number: Math.random().toFixed(2),
invoice_num: "#1232",
bill_from: "AMD Inc.",
bill_to: "Intel Inc.",
total_cost: "14630",
status: "Delivered",
order_date: "February 17th 2018",
bill_from_email: "pineapple#company.com",
bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
bill_from_phone: "+(402) 748-3970",
bill_from_fax: "",
bill_to_email: "redq#company.com",
bill_to_address: "405 Mulberry Rd, Mc Grady, NC, 28649",
bill_to_phone: "+(740) 927-9284",
bill_to_fax: "+0(863) 228-7064",
ITEMS: [
{
item_name: "Unicorn Tears",
unit_costs: "500",
unit: "14",
price: "1700",
sub_total: "133300",
vat: "13300",
grand_total: "14630",
},
],
},
{
id: Math.random().toString(),
number: Math.random().toFixed(2),
invoice_num: "#1232",
bill_from: "Apple Inc.",
bill_to: "Samsung",
total_cost: "14630",
status: "Shipped",
order_date: "February 17th 2018",
bill_from_email: "pineapple#company.com",
bill_from_address: "86781 547th Ave, Osmond, NE, 68765",
bill_from_phone: "+(402) 748-3970",
bill_from_fax: "",
bill_to_email: "redq#company.com",
bill_to_address: "405 Mulberry Rd, Mc Grady, NC, 28649",
bill_to_phone: "+(740) 927-9284",
bill_to_fax: "+0(863) 228-7064",
ITEMS: [
{
item_name: "Rainbow Machine",
unit_costs: "700",
unit: "5",
price: "3500",
sub_total: "133300",
vat: "13300",
grand_total: "14630",
},
],
},
];
AddInvoiceItem.js file where you can find inputs and adding logic.
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { uiActions } from "../../store/ui-slice";
import classes from "./AddInvoiceItem.module.css";
import { useFormik } from "formik";
import Wrapper from "../../UI/Wrapper";
import Card from "../../UI/Card";
import Footer from "../../UI/Footer";
import Button from "../../UI/Button";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCalendar } from "#fortawesome/free-solid-svg-icons";
import { faEllipsis } from "#fortawesome/free-solid-svg-icons";
import { invoiceActions } from "../../store/invoice-slice";
import { Link } from "react-router-dom";
const AddInvoiceItem = (props) => {
const date = new Date();
const options = ["Pending", "Shipped", "Delivered"];
const inputs = [{ itemName: "", unitCosts: "", unit: "" }];
const [startDate, setStartDate] = useState(date);
const [selectedOption, setSelectedOption] = useState(options[0]);
const [listItems, setListItems] = useState(inputs);
const optionClickHandler = (value) => () => {
setSelectedOption(value);
dispatch(uiActions.toggleMoreOptions());
};
const addInvoiceHandler = (invoice) => {
console.log(invoice);
console.log(selectedOption);
dispatch(
invoiceActions.addNewInvoice({
id: Math.random(),
invoiceNumber: invoice.invoiceNumber,
billFrom: invoice.billFrom,
billFromAddress: invoice.billFromAddress,
billTo: invoice.billTo,
billToAddress: invoice.billToAddress,
status: selectedOption,
order_date: startDate.toJSON(),
ITEMS: [
{
id: Math.random(),
item_name: invoice.itemName,
unit_costs: invoice.unitCosts,
units: invoice.unit,
},
],
})
);
};
const formikInvoice = useFormik({
initialValues: {
invoiceNumber: "",
billFrom: "",
billFromAddress: "",
billTo: "",
billToAddress: "",
status: selectedOption,
date: startDate.toJSON(),
ITEMS: [
{
id: Math.random(),
itemName: "",
unitCosts: "",
unit: "",
},
],
},
onSubmit: (val) => {
addInvoiceHandler(val);
},
});
const dispatch = useDispatch();
const toggleMoreOptions = () => {
dispatch(uiActions.toggleMoreOptions());
};
const showOtherOptions = useSelector(
(state) => state.ui.selectMoreOptionsIsVisible
);
let counter = 1;
const addItemHandler = () => {
setListItems(listItems.concat([{ itemName: "", unitCosts: "", unit: "" }]));
};
return (
<form onSubmit={formikInvoice.handleSubmit}>
<Wrapper isShrinked={props.isShrinked}>
<Card>
<div className={classes.content}>
<div className={classes["buttons-wrapper"]}>
<Link to="/invoices">
<button type="button" className={classes["cancel-btn"]}>
Cancel
</button>
</Link>
{/* <Link to="/invoices"> */}
<Button className={classes["save-btn"]}>Save</Button>
{/* </Link> */}
</div>
<div className={classes["invoice-info-wrapper"]}>
<div className={classes["invoice-info"]}>
<h3>Invoice Info</h3>
<input
placeholder="Number"
type="text"
name="invoiceNumber"
id="invoiceNumber"
onChange={formikInvoice.handleChange}
value={formikInvoice.values.invoiceNumber}
onBlur={formikInvoice.handleBlur}
></input>
</div>
<div className={classes["right-side-column"]}>
<div className={classes["order-status"]}>
<span>Order Status: </span>
<div className={classes.buttons}>
{showOtherOptions && (
<ul className={classes.options}>
{options.map((option) => (
<li
onClick={optionClickHandler(option)}
key={Math.random()}
>
{option}
</li>
))}
</ul>
)}
<button type="button" className={classes.status}>
{selectedOption}
</button>
<button
type="button"
className={classes.dots}
onClick={toggleMoreOptions}
>
<FontAwesomeIcon icon={faEllipsis} />
</button>
</div>
</div>
<div className={classes["order-date"]}>
<span>Order Date:</span>
<DatePicker
className={classes["order-date-input"]}
selected={startDate}
onChange={(val) => setStartDate(val)}
/>
<FontAwesomeIcon
icon={faCalendar}
className={classes.calendar}
></FontAwesomeIcon>
</div>
</div>
</div>
<div className={classes["order-bills"]}>
<div className={classes["bill-from"]}>
<input
placeholder="Bill From"
type="text"
name="billFrom"
id="billFrom"
onChange={formikInvoice.handleChange}
value={formikInvoice.values.billFrom}
onBlur={formikInvoice.handleBlur}
></input>
<textarea
placeholder="Bill From Address"
name="billFromAddress"
id="billFromAddress"
onChange={formikInvoice.handleChange}
value={formikInvoice.values.billFromAddress}
onBlur={formikInvoice.handleBlur}
></textarea>
</div>
<div className={classes["bill-to"]}>
<input
placeholder="Bill To"
type="text"
name="billTo"
id="billTo"
onChange={formikInvoice.handleChange}
value={formikInvoice.values.billTo}
onBlur={formikInvoice.handleBlur}
></input>
<textarea
placeholder="Bill To Address"
name="billToAddress"
id="billToAddress"
onChange={formikInvoice.handleChange}
value={formikInvoice.values.billToAddress}
onBlur={formikInvoice.handleBlur}
></textarea>
</div>
</div>
<div className={classes["table-wrapper"]}>
<table>
<colgroup>
<col className={classes.col1}></col>
<col className={classes.col2}></col>
<col className={classes.col3}></col>
<col className={classes.col4}></col>
<col className={classes.col5}></col>
<col className={classes.col6}></col>
</colgroup>
<thead>
<tr>
<td className={classes["more-padding"]}>#</td>
<td>Item Name</td>
<td>Unit Costs</td>
<td>Unit</td>
<td>Price</td>
<td></td>
</tr>
</thead>
<tbody>
{listItems.map((item, index) => (
<tr key={index}>
<td className={classes["more-padding"]}>{counter++}</td>
<td>
<input
placeholder="Item Name"
className={classes.inputs}
name="itemName"
id="itemName"
onChange={formikInvoice.handleChange}
value={formikInvoice.values.item_name}
onBlur={formikInvoice.handleBlur}
></input>
</td>
<td>
<input
placeholder="Unit Costs"
className={classes.inputs}
name="unitCosts"
id="unitCosts"
onChange={formikInvoice.handleChange}
value={formikInvoice.values.unit_costs}
onBlur={formikInvoice.handleBlur}
></input>
</td>
<td>
<input
placeholder="Unit"
className={classes.inputs}
name="unit"
id="unit"
onChange={formikInvoice.handleChange}
value={formikInvoice.values.units}
onBlur={formikInvoice.handleBlur}
></input>
</td>
<td>0</td>
<td></td>
{/* There should be dynamic values later */}
</tr>
))}
</tbody>
</table>
<div className={classes["add-item-btn"]}>
<button
onClick={addItemHandler}
type="button"
className={classes["add-item-btn"]}
>
Add Item
</button>
</div>
<div className={classes.total}>
<p className={classes["sub-total"]}>
<span>Sub Total: </span>
<span>$0</span>
{/* Dynamic value later here */}
</p>
<div className={classes["total-vat"]}>
<span>Total Vat:</span>
<div className={classes["total-sum"]}>
<span className={classes["input-wrapper"]}>
<input type="text" defaultValue="10"></input>
<span>%</span>
</span>
<span className={classes.sum}>$0</span>
{/* Dynamic value later here */}
</div>
</div>
<div className={classes["grand-total"]}>
<h3>Grand Total</h3>
<div className={classes.input}>
<input type="text" defaultValue="$"></input>
<span>0</span>
{/* Dynamic value later here */}
</div>
</div>
</div>
</div>
<div className={classes.dummy}></div>
</div>
</Card>
<Footer />
</Wrapper>
</form>
);
};
export default AddInvoiceItem;
invoice-slice.js file.
import { createSlice } from "#reduxjs/toolkit";
import { INVOICES_LIST } from "../Pages/Invoice/InvoicesList";
const invoiceSlice = createSlice({
name: "invoice",
initialState: {
invoices: INVOICES_LIST,
},
reducers: {
addNewInvoice(state, action) {
const newItem = action.payload;
state.invoices.push({
id: newItem.id,
billFrom: newItem.bill_from,
billFromAddress: newItem.billFromAddress,
billTo: newItem.bill_to,
billToAddress: newItem.bill_to_address,
invoiceNumber: newItem.invoice_num,
status: newItem.status,
order_date: newItem.order_date,
ITEMS: [
{
id: Math.random(),
item_name: newItem.item_name,
unit_costs: newItem.unit_costs,
units: newItem.units,
},
],
});
console.log(newItem);
},
removeInvoice(state, action) {
const id = action.payload;
state.invoices = state.invoices.filter((item) => item.id !== id);
},
editInvoice() {},
},
});
export const invoiceActions = invoiceSlice.actions;
export default invoiceSlice;
Also: should i give every item unique id? Because, i'm not sure if i need that.
P.S. here is github repo - https://github.com/stepan-slyvka/test-project
P.P.S. this is my PET project, so don't wonder why I'm using Context in one page and another Redux. It's only for learning purpose.
I've looked at your code now. This is one way to get it all to work, but, in general you should probably make a decision whether to use formik's state as the main state for listItems and refactor your code into that direction or do something else.
However, here is one ROUGH way to solve your problem, use it as a reference in making decisions on how to manage it.
Add the following to your AddinvoiceItem.js:
const updateItemHandler = (index, inputName, value) => {
listItems[index] = {...listItems[index], [inputName]: value};
}
const updateValuesOnSubmit = () => {
return listItems;
}
and change every input (itemName, unitCosts, unit) to use the new function in its onChange parameter:
onChange={(e) => updateItemHandler(index, 'THIS has to be the name of the input, for example itemName', e.currentTarget.value)}
Change addInvoiceHandler to use new function in its dispatch:
dispatch(
invoiceActions.addNewInvoice({
id: Math.random(),
invoiceNumber: invoice.invoiceNumber,
billFrom: invoice.billFrom,
billFromAddress: invoice.billFromAddress,
billTo: invoice.billTo,
billToAddress: invoice.billToAddress,
status: selectedOption,
order_date: startDate.toJSON(),
ITEMS: [
...updateValuesOnSubmit()
],
})
);
};
and finally, go to invoice-slice.js and do this:
ITEMS: [
...newItem.ITEMS
],
This is how you should be able to get multiple items within an invoice to your redux state. Let me emphasize once more that you should still consider using Formik's state and its management, handleChange features etc. as you've already chosen it for this task.
But this is still one way to do what you wanted to do. Hope it helps!
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.
How can I display the image inside the object in React.js?
const Area = () =>{
const flags = [
{ id: 1, name: "USA", image: "./us.png" },
{ id: 2, name: "Canada", image: "./ca.png" },
];
return (
<div>
{flags.map((area) => {
return <img key={area.id} src={area.image} />;
})}
</div>
)}
The very first thing that you need to do is wrap the src in {} which you're doing already.
Then if you're using webpack. Instead of : <img src={"./us.png"} />
You need to use require() like this <img src={require('./us.png')} />
const Area = () => {
const flags = [
{ id: 1, name: "USA", image: "./us.png" },
{ id: 2, name: "Canada", image: "./ca.png" },
]
return (
<div>
{flags.map((area) => {
return <img key={area.id} src={require(area.image)} />;
})}
</div>
)}
}
Another option would be to first import the image as shown below
import ca from './us.png' or const us = require('./us.png).
Then add them to your flags object like shown below:
import usa from './us.png;
import canada from './ca.png;
const Area = () => {
const flags = [
{ id: 1, name: "USA", image: usa },
{ id: 2, name: "Canada", image: canada },
]
return (
<div>
{flags.map((area) => {
return <img key={area.id} src={area.image} />;
})}
</div>
)}
}
You can use require for images inside an object.But your code works fine even without that you just have to return it inside return method().
const Area = () =>{
const flags = [
{ id: 1, name: "USA", image: require('./us.png') },
{ id: 2, name: "Canada", image: require('./ca.png') },
]
return (
<div>
{flags.map((area) =>
<img key={area.id} src={area.image} />
)}
</div>
)
}
return(
Area()
)
It works fine this way as well
const Area = () =>{
const flags = [
{ id: 1, name: "USA", image: "./us.png" },
{ id: 2, name: "Canada", image: "./ca.png" },
]
return (
<div>
{flags.map((area) =>
<img key={area.id} src={area.image} />
)}
</div>
)
}
return(
Area()
)
As I mentioned in my comments that please double check the path of the image. Image path might be a problem else it is looking fine. Moreover, you can press ctrl+click on path to route to that folder or check it manually.
I am making dynamic menus using a recursive function and I have already made the menus and it display in the right order without any issues.
And I receive the data for menu from service.js file and you can see the entire working application in the below code sandbox example,
https://codesandbox.io/s/reactstrap-accordion-3uoz9
Requirement:
Here I am in the need to find out the last level of menus and need to assign checkbox with value as their respective id {item.id}.
Eg:
For First menu one,
-> [Checkbox with value as 1.1.1] One-One-One
-> [Checkbox with value as 1.1.2] One - one - two
-> [Checkbox with value as 1.1.3] One - one - three
For Second menu two,
-> [Checkbox with value as 2.1] Two - one
.
.
.
For sixth menu six,
-> [Checkbox with value as 6] Six
I hope the point is clear that I need to find out the last level in recursion and should assign a checkbox to it with the value of their id.
Please fork the code sandbox provided and help me to achieve the result of making the checkbox at the last level.
Optional requirement:
The collapse is working for whole menus at once if possible please make it collapse at each individual level in unique.
But the main important requirement is to make a checkbox at the last level of menus.
A big thanks in advance...
Edit:
As commented by Crowder, I have created the snippet removing reactstrap code and it is okay now because I am in the need of displaying checkbox inline to laste level of submenus (last children elements).
const menuData = [
{
id: "1",
name: "One",
children: [
{
id: "1.1",
name: "One - one",
children: [
{ id: "1.1.1", name: "One - one - one" },
{ id: "1.1.2", name: "One - one - two" },
{ id: "1.1.3", name: "One - one - three" }
]
}
]
},
{
id: "2",
name: "Two",
children: [{ id: "2.1", name: "Two - one" }]
},
{
id: "3",
name: "Three",
children: [
{
id: "3.1",
name: "Three - one",
children: [
{
id: "3.1.1",
name: "Three - one - one",
children: [
{
id: "3.1.1.1",
name: "Three - one - one - one",
children: [
{ id: "3.1.1.1.1", name: "Three - one - one - one - one" }
]
}
]
}
]
}
]
},
{ id: "4", name: "Four" },
{
id: "5",
name: "Five",
children: [
{ id: "5.1", name: "Five - one" },
{ id: "5.2", name: "Five - two" },
{ id: "5.3", name: "Five - three" },
{ id: "5.4", name: "Five - four" }
]
},
{ id: "6", name: "Six" }
];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentSelection: "",
menuItems: [],
isToggleOpen: false
};
}
componentDidMount() {
this.setState({ menuItems: menuData });
}
handleClick(id, evt) {
evt.preventDefault();
console.log("click handler called with", id);
this.setState({ currentSelection: id });
}
toggle() {
console.log(this.state);
this.setState({
isToggleOpen: !this.state.isToggleOpen
});
}
buildMenu(items) {
return (
<ul>
{items &&
items.map(item => (
<li key={item.id}>
<div>
{item.name}
{item.children && item.children.length > 0
? this.buildMenu(item.children)
: null}
</div>
</li>
))}
</ul>
);
}
render() {
return (
<div>
<h2>Click any of the below option</h2>
{this.state.menuItems &&
this.state.menuItems.map((item, index) => {
return (
<div key={index}>
{item.name}
{this.buildMenu(item.children)}
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/4.8.0/reactstrap.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/4.8.0/reactstrap.min.css" />
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
buildMenu(items) {
return (
<ul>
{items &&
items.map((item, index) => (
<li key={item.id}>
<div>
{this.state.isToggleOpen}
{(item.children) ? 'Not Last': 'Here you can apply your check box'} //this check if item have children
<Button onClick={this.toggle.bind(this)}> {item.name} {index} </Button>
<Collapse isOpen={this.state.isToggleOpen}>
{item.children && item.children.length > 0
? this.buildMenu(item.children, index)
: null}
</Collapse>
</div>
</li>
))}
</ul>
);
}
Now second case on render
<Button onClick={this.toggle.bind(this)}> {item.name}</Button>
Check if item have children
{(item.children) ? this.buildMenu(item.children) : 'Apply your checkbox here'}
Full Code
import React from "react";
import { render } from "react-dom";
import { loadMenu } from "./service";
import { Button, Collapse } from "reactstrap";
// const buildMenu = function buildMenu(items)
// const Menu = ({ items }) => buildMenu(items);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentSelection: "",
menuItems: [],
isToggleOpen: false
};
}
componentDidMount() {
loadMenu().then(items => this.setState({ menuItems: items }));
}
handleClick(id, evt) {
evt.preventDefault();
console.log("click handler called with", id);
this.setState({ currentSelection: id });
}
toggle() {
console.log(this.state);
this.setState({
isToggleOpen: !this.state.isToggleOpen
});
}
buildMenu(items) {
return (
<ul>
{items &&
items.map(item => (
<li key={item.id}>
<div>
{this.state.isToggleOpen}
{(item.children) ? 'Not Last': 'Here you can apply your check box'}
<Button onClick={this.toggle.bind(this)}> {item.name} </Button>
<Collapse isOpen={this.state.isToggleOpen}>
{item.children && item.children.length > 0
? this.buildMenu(item.children)
: null}
</Collapse>
</div>
</li>
))}
</ul>
);
}
render() {
console.log(this.state.menuItems);
return (
<div>
<h2>Click any of the below option</h2>
{this.state.menuItems &&
this.state.menuItems.map((item, index) => {
return (
<div>
<Button onClick={this.toggle.bind(this)}> {item.name} </Button>
{(item.children) ? 'Not Last': 'Here you can apply your check box'}
<Collapse isOpen={this.state.isToggleOpen}>
{this.buildMenu(item.children)}
</Collapse>
</div>
);
})}
</div>
);
}
}
render(<App />, document.getElementById("root"));
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>
)
}