Loops inside react components leading to rendering of sub components - javascript

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.

Related

How to update input value on click of button from another page?

In React project I've certain list of data populated in grid component. The grid has columns of varied data types like text, input field, link etc. In one such column an input field is mapped which has its respective default values.
Now my intention is to change name of that particular input field of a particular record which navigates to another page which, has a button when clicked changes the name on the grid, but, the name of all records are changed. I need to change name of only that single record which is clicked and navigated based on its 'id'. Please refer to the code below.
const newCompData = [
{
id: 1,
comp: "McDonalds",
feedback: "Best Food Chain",
name: "Mike John",
est: "YYYY/MM",
store: "Burger Store"
},
{
id: 2,
comp: "KFC",
feedback: "Best Chicken Products",
store: "Chicken Food",
name: "Steve Williams",
est: "YYYY/MM"
},
{
id: 3,
comp: "Dominos",
feedback: "Best Pizza Store",
store: "Pizza Store",
name: "Mark Rays",
est: "YYYY/MM"
},
{
id: 4,
comp: "Star Bucks",
feedback: "Best Coffee Store",
store: "Coffee Store",
name: "Patrick Right",
est: "YYYY/MM"
},
{
id: 5,
comp: "Burger King",
feedback: "Best Burgers",
store: "Burger Store",
name: "Williams Wills",
est: "YYYY/MM"
},
{
id: 6,
comp: "Lays",
feedback: "Best Chips Factory",
store: "Chips Store",
name: "Sam Andrews",
est: "YYYY/MM"
}
];
const [dataAll, setDataAll] = useState([]);
useEffect(() => {
const newData = newCompData?.map((data) => {
return [
{ id: data.id },
data.comp,
data.store,
data.name,
data.est,
data.feedback
];
});
setDataAll(newData);
}, []);
return (
<div className="App">
<Table newData={dataAll} />
</div>
);
};
As seen above.... a list of records are mapped into the table component
Here is the table component, where all the data is mapped
const Table = ({ newData }) => {
useEffect(() => {
setGridData({
data: newData,
page_info: {
total_pages: 5,
current_page: 1
}
});
}, [newData]);
let GridConfig = {};
GridConfig = TableConfig;
const [gridConfigData, setGridConfigData] = useState(GridConfig);
const [gridData, setGridData] = useState(newData);
return (
<>
<Grid GridConfig={gridConfigData} GridData={gridData} />
</>
);
};
Following is the Grid component in table format (only showing the required part)
const Grid = (props) => {
let colConfig = props.GridConfig.column_config;
let gridData = props.GridData?.data;
const { newValue } = useContext(GlobalContext);
const navigate = useNavigate();
return (
....
....
....
{colConfig[cIndex].data_type === "input_text" &&
!colConfig[cIndex].cell_click_callback && (
<input
type="text"
defaultValue={
newValue != undefined ? newValue : colData
}
/>
)}
....
....
....
)
}
export default Grid
This is the page when clicked on record is navigated here.
const Test = () => {
const location = useLocation();
const navigate = useNavigate();
const newId = location.state?.id;
const { setNewValue } = useContext(GlobalContext);
const handleClick = () => {
navigate("/");
// When clicked on this setValue shows the same value across all records
setNewValue("John Spencer");
};
return (
<>
<h2>Test Page</h2>
<p>ID: {newId}</p>
<button onClick={handleClick}>Go Back</button>
</>
);
};
I want to show the value for input text which was clicked on Test page. Only specific record should be updated. What is the best solution to tackle this issue?
Please refer to the Codesandbox link: https://codesandbox.io/s/elated-varahamihira-xpjtdb

React JS. How can I display the image inside the object?

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.

use ES6 .map() on multiple times nested objects for react components

How can I iterate through this object using .map():
state = {
contacts: [
{ "id":1,
"name":"Leanne Graham",
"email":"Sincere#april.biz",
"address":{
"street":"Kulas Light",
"city":"Gwenborough",
"geo":{
"lat":"-37.3159",
"lng":"81.1496"
}
},
"phone":"1-770-736-8031",
},
{ "id":2,
"name":"Ervin Howell",
"email":"Shanna#melissa.tv",
"address":{
"street":"Victor Plains",
"city":"Wisokyburgh",
"geo":{
"lat":"-43.9509",
"lng":"-34.4618"
}
},
"phone":"010-692-6593",
}
]
}
so map over the contacts will work because is an array and all data like id, name, email and phone is accessible but if I want to iterate over the address, is crashing. I have used some example like:
render(){
const {contacts} = this.state
return(
<>
{Object.keys(contacts.address).map((address, index) => (
<span className="d-block" key={index}>{contacts.address[address]}</span>
))}
</>
);
}
which should work with address but is crashin on geo{} and at this point I have lost the signal.
Anyone can give me an ideea ?
This should help:
const address = contacts[0].address;
<>
{Object.keys().map((addressKey, index) => (
<span className="d-block" key={index}>
{typeof address[addressKey] === "object"
? Object.keys(address[addressKey]).map(e => (
<span>{address[addressKey][e]}</span>
))
: contacts[0].address[address]}
</span>
))}
</>;
I don't think is a matter as long as it displays them
After you mapped the contacts you can excess the addresses properties as you like:
const contacts = [
{
id: 1,
name: "Leanne Graham",
email: "Sincere#april.biz",
address: {
street: "Kulas Light",
city: "Gwenborough",
geo: {
lat: "-37.3159",
lng: "81.1496",
},
},
phone: "1-770-736-8031",
},
{
id: 2,
name: "Ervin Howell",
email: "Shanna#melissa.tv",
address: {
street: "Victor Plains",
city: "Wisokyburgh",
geo: {
lat: "-43.9509",
lng: "-34.4618",
},
},
phone: "010-692-6593",
},
];
const addresses = contacts.map(({ address, id }) => ({
id,
...address,
}));
console.log(addresses);
Like rendering them:
addresses.map(({ street, city, id }) => (
<span className="d-block" key={id}>
{street}:{city}
</span>
))
You can map over an array because you expect it to have consistent values through each element, but that is not really the case for object. All the values are different and have different meaning. Also, your span will not be able to display objects, it will only primitive values such as strings or numbers
You can do what you want to achieve manually.
const { contacts } = this.state;
return (
<>
{contacts.map(({ address }, id) => {
return (
<React.Fragment key={id}>
<span>Street: {address.street}</span>
<span>City: {address.city}</span>
<span>Lat: {address.geo.lat}</span>
<span>Lng: {address.geo.lng}</span>
</React.Fragment>
);
})}
If you really want to do it using a loop or some form of iteration you can look into Object.entries. But it would be really difficult to do that with nested objects if you do not know what you are dealing with.
contacts.map(({ address }) => {
for (let [key, value] of Object.entries(address)) {
console.log(`${key}: ${value}`); // logs "street: Kulas Light"
}
})
but note for geo it will give "geo: [Object object]" if you put this directly into a span.
P.S I would recommend finding a better key than the index of array for the Fragment.

ReactJS two way binding not working, somewhere wrong with binding

I'm new to reactJs, I'm not sure where it went wrong.
I suppose there is something wrong with binding input. I suppose, cant change input because of value={detail.name}. However, even though I have deleted value={detail.name}, Name: {detail.name} still keeps the original value.
Could somebody give me a hint?
import React, { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
};
}
changeName(event) {
this.setState({
name: event.target.value
});
}
onDelete() {}
render() {
return (
<div>
<ul>
{this.state.details.map((detail, index) => (
<li key={index}>
Name: {detail.name} | age: {detail.age}
<input
style={{ marginLeft: "10px" }}
type="text"
onChange={this.changeName.bind(this)}
value={detail.name}
/>
</li>
))}
</ul>
</div>
);
}
}
export default App;
I updated the code a bit.
First of all, I moved the binding of the callback to the constructor (to have ONE callback instead of one per item*render)
I also changed the key used in the map to be the id, rather than the index of the current item.
Try, it, I hope it works for you.
import React, { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
};
this.changeName = this.changeName.bind(this);
}
changeName(event) {
const {target} = event;
const id = Number(target.dataset.id);
const { details } = this.state;
this.setState({
details: details.map((detail) => {
if (detail.id === id) {
return {
...detail,
name: target.value,
}
}
return detail;
}),
});
}
onDelete() {}
render() {
return (
<div>
<ul>
{this.state.details.map(({ id, age, name }) => (
<li key={id}>
Name: {name} | age: {age}
<input
style={{ marginLeft: "10px" }}
type="text"
onChange={this.changeName}
data-id={id}
value={name}
/>
</li>
))}
</ul>
</div>
);
}
}
export default App;
Your code works fine, nothing wrong with the input data binding. The problem is you're setting the name property directly to the state object. That would make it go from this:
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
]
}
To this:
this.state = {
details: [
{ id: 1, name: "Tom", age: "20" },
{ id: 2, name: "zhunan", age: "22" },
{ id: 3, name: "kobe", age: "35" }
],
name: "Bob"
}
Which has no effect on how the component gets rendered. To properly change the name of one of the details, which is what I assume you want, you also need to do a find that detail object to modify. Like this:
changeName(e, target_detail) {
this.setState({
// always update the WHOLE STATE OBJECT! using a map
details: this.state.details.map(detail => {
// the detail we want to modify has the same ID
if(detail.id === target_detail.id) {
// modify the name value of only that
target_detail.name = e.target.value
}
})
});
}
render method:
render() {
return (
<div>
<ul>
{this.state.details.map((detail, index) => (
<li key={index}>
Name: {detail.name} | age: {detail.age}
<input
style={{ marginLeft: "10px" }}
type="text"
// arrow functions implicitly "bind" the current this context:
onChange={e => this.changeName(e, detail)}
value={detail.name}
/>
</li>
))}
</ul>
</div>
);
}

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