How do I put my props object into an array and iterate through it?
I have a bootstrap navigation bar that has dropdowns. My code scans the javascript object and if the key dropdown is found, it will create a dropdown menu from the data in the javascript object from the dropdown section.
My Javascript Object:
var linksNav = {
items: [
{
"type": "link",
"title": "Events",
"href": "#",
"target": "_self"
},
{
"type": "link",
"title": "Groups",
"href": "#",
"target": "_self",
"dropdown":
{
"dropItems":
[
{
"left": "1st left",
"left-option": ["1","2"]
},
{
"left": "2nd left",
"left-option": ["1","2"]
},
{
"left": "3rd left",
"left-option": ["1","2"]
},
]
}
},
{
"type": "heading",
"title": "Capabilities",
"href": "#",
"target": "_self"
},
]
}
This is my LinksNav class which creates the navigation bar by going through the javascript object. If a dropdown item is detected in my javascript object, it will pass it to my Navsub component:
LinksNav:
var LinksNav = React.createClass({
render: function() {
let navDrop;
if (this.props.isDropdown) {
navDrop = (
<ul className="dropdown-menu fade">
<div>{this.props.isDropdown.dropItems[0].left}></div>
<Navsub isDropdown = {this.props.isDropdown}/>
</ul>
)
}
return (
<li className={this.props.title + ' nav-items'}>
{this.props.title}
{navDrop}
</li>
);
}
});
Navsub:
This is my Navsub class where I try to spit out the data from my dropdown list in my Javascript object if it can find it. :
var Navsub = React.createClass({
render: function() {
var itemsLeft= [];
for (var j = 0; j < this.props.isDropdown.dropItems.length; j++) {
itemsLeft.push(<Navsubrightitems key={j} type={this.props.isDropdown.dropItems[j].left} />);
}
return (
<div className="group-dropdown-menu">
<div className="dropdown-left-menu">
{this.props.isDropdown.dropItems[0].left}
{this.props.isDropdown.dropItems[1].left}
</div>
<div className="dropdown-right-menu">
{itemsRight}
</div>
</div>
);
}
});
I can successfully grab the dropdown list from the json object throuhh props, as seen by my hardcoding:
{this.props.isDropdown.dropItems[0].left}
{this.props.isDropdown.dropItems[1].left}
, but I want to iterate through it so that I get the value of left under dropItems by putting it in an array and then spitting out those values. I attempted to do so by iterating it through this.props.isDropdown.dropItems.length, but seems that's not valid as I get an undefined error.
It seems that the props dropDownItems are not initially present and hence you need to check for and undefined value and then you can use map to iterate over these. You can do it as follows
var Navsub = React.createClass({
render: function() {
return (
<div className="group-dropdown-menu">
<div className="dropdown-left-menu">
{this.props.isDropdown.dropItems[0].left}
{this.props.isDropdown.dropItems[1].left}
</div>
<div className="dropdown-right-menu">
{this.props.isDropdown.dropItems && this.props.isDropdown.dropItems.map((listItem, index) => {
return <Navsubrightitems key={index} type={listItem.left} />
})}
</div>
</div>
);
}
});
I think, this is what you want, Write it like this:
{
this.props.isDropdown.dropItems && this.props.isDropdown.dropItems.map((item,i) => {
return <Navsubrightitems key={i} type={item.left} />
})
}
Related
I have a dynamic json object which contains a button element. I am using createElement and material UI to render the object data.
I wanted to apply customizable CSS using className in the button component, but I couldn't achieve it.
So, how can I apply CSS in runtime to the material UI component?
Here is my code snippet:
import React, { useState } from "react";
import './App.css';
import { Button, Grid, Checkbox, TextField, Switch, Link } from '#mui/material';
const KeysToComponentMap = {
button: Button,
grid: Grid,
checbox: Checkbox,
text: TextField,
switch: Switch,
link: Link
};
const RenderCard = (props) => {
const SampleData =
[
{
"type": "button",
"display": "RELEASE THE BATCH",
"key": "RS",
"class": "btn",
"value": [
{
"type": "button",
"display": "CONFIRM AND SUBMIT",
"key": "key1",
"value": "post"
}
]
},
{
"type": "button",
"display": "RELEASE THE OBSERVATION",
"key": "RO",
"value": [
{
"type": "input",
"display": "Observation",
"key": "key8",
"value": "val1"
},
{
"type": "button",
"display": "Done",
"key": "key9",
"value": [
{
"type": "button",
"display": "CONFIRM AND SUBMIT",
"key": "key10",
"value": "post"
}
]
}
]
}
]
}
return (
<>{
{SampleData.map((item, index) => {
console.log(item.type);
console.log(item.class)
if (typeof KeysToComponentMap[item.type] !== "undefined") {
return (
React.createElement(
KeysToComponentMap[item.type],
{
onClick: () => { onButtonHandler(item, item.key) },
variant: "contained",
type: "input",
className: KeysToComponentMap[item.class],
},
item.display &&
(typeof item.display === "string"
? item.display
: item.value.map(c => ActivityDetail(c)))
))
}
})}
}
</>)
}
export default RenderCard;
Original Question:
Your problem isn't that the className isn't being applied, it's that the value that you're trying to assign from KeysToComponentMap (KeysToComponentMap[item.class]) is returning undefined. I had to modify your code, and add a class property to your Sample JSON, to get it to work so you may want to change it however you need it:
{
onClick: () => {
onButtonHandler(item, item.key);
},
variant: "contained",
type: "input",
className: item.class // Changed from `className: KeysToComponentMap[item.class]`
},
Working Code Sandbox: https://codesandbox.io/s/classname-cjlqo?file=/demo.js
Question from Comments:
in case if i want to pass style directly instead of classname then in
what format should be style property be in sample JSON. I was passing
style in json like: [{"style": "color: 'red'"}] but it is a wrong way
that's why not working. So, what is the correct way?
Since you are using MUI 5, the correct prop to pass inline styles is sx. You may want to make your configuration a bit more flexible by just passing whatever additional properties each component might need straight through and spreading them into the component directly. For example:
// Sample config:
{
type: "button",
display: "RELEASE THE BATCH",
key: "RS",
className: "my-button",
sx: { color: "blue" },
value: [
{
type: "button",
display: "CONFIRM AND SUBMIT",
key: "key1",
value: "post"
}
]
},
// Renderer
<>
{SampleData.map(({ type, key, display, value, ...rest }, index) => {
console.log(type);
if (typeof KeysToComponentMap[type] !== "undefined") {
return React.createElement(
KeysToComponentMap[type],
{
onClick: () => {
onButtonHandler(item, key);
},
variant: "contained",
type: "input",
...rest
},
display &&
(typeof display === "string"
? display
: value.map((c) => ActivityDetail(c)))
);
}
})}
</>
Working example with className and style: https://codesandbox.io/s/style-e4inc?file=/demo.js
I have a list of items that needs to be recursively rendered. I am able to render it upto first level. Can someone help me with the nested recursion. I have posted the relevant functions in a function component. Note that details has been stored in a state variable details
{
"details": [{
"title": "Part-1",
"content": [{
"type": "text",
"content": "TextABC"
}]
}, {
"title": "Part-2",
"content": [{
"type": "text",
"content": "TextXYZ"
}, {
"type": "list",
"content": [{
"text": "TextLMN",
"type": "text"
}, {
"type": "list",
"content": [{
"text": "TextPQR",
"type": "text"
}, {
"text": "TextDEF",
"type": "text"
}]
}]
}]
}, {
"title": "Part-3",
"content": [{
"type": "list",
"content": ["<a target='_blank' href='https://www.example.com'>ABC<\/a>", "<a target='_blank' href='https://www.example1.com'>XYZ<\/a>"]
}]
}]
}
Each Item is referenced either with a type that can be text or a list. I have tried the following, but the items with nested list is not working
const isAnchor = str => {
return /^\<a.*\>.*\<\/a\>/i.test(str);
};
const getContentJsx = (value) => {
return isAnchor(value) ? (
<li dangerouslySetInnerHTML={{ __html: sanitize(value) }} />
) : (
<li>{value}</li>
);
};
const getDetailJsx = () => {
return details.map(({ title, content }, index) => {
return (
<div key={`${title}${index}`}>
<h6>
<span>{title}</span>
</h6>
{content?.map(({ type: mainType, content: data }) => (
<div>
{mainType === "text" && <p>{data}</p>}
{mainType === "list" && <ul>{data?.map((contentValue) => getContentJsx(contentValue))}</ul>}
</div>
))}
</div>
);
});
};
return (
<div>
<>
{getDetailJsx()}
</>
)}
</div>
);
As suggested, you can create a recursive component that wraps the logic and makes it possible to call recursively. First, I had to correct your detail object. It has inconsistency within its attributes. Note, for example, text -> content:
"type": "text",
"text": "TextPQR"
to
"type": "text",
"content": "TextPQR"
Moreover, observe that it was separated the part that involves the title from the recursion to facilitate its comprehension and make code cleaner.
const getContentJsx = (value) => {
return isAnchor(value) ? (
<li dangerouslySetInnerHTML={{ __html: sanitize(value) }} />
) : (
<li>{recursive(value)}</li>
);
};
const getDetailJsx = () => {
return details.map(({ title, content }, index) => {
return (
<div key={`${title}${index}`}>
<h3>
<span>{title}</span>
</h3>
{recursive(content)}
</div>
)
})
}
const recursive = (content) => {
return (
<div>
{
content.map(({type: mainType, content: data}, index) => (
<div key={index}>
{mainType === "text" && <p>{data}</p>}
{mainType === "list" && <ul>{getContentJsx(data)}</ul>
}
</div>
))
}
</div>
)
Also as shown here
I have JSON data hundreds of entries like this:
{
"product":"Protec",
"type":"Central Opening",
"attribute":"Triple Lock",
"height":"2100",
"width":"1600",
"price":"3000"
},
{
"product":"Protec",
"type":"Sliding Door",
"attribute":"Single Lock",
"height":"2100",
"width":"1600",
"price":"3000"
},
{
"product":"ForceField",
"type":"Hinge Door",
"attribute":"Triple Lock",
"height":"2300",
"width":"1200",
"price":"100"
},
my vue component
var distinct_product = new Vue({
el: '#distinct',
data:{
distinct_product: [],
all_products: []
},
I fetch it and store it in my vue component and store it in a second data so when I render it to the ui the user only sees distinct elements.
mounted: async function(){
fetch("/Data/products.json")
.then(res => res.json())
.then(res => {
this.all_products = res
this.distinct_product = res
var disProduct = [...new Set(this.distinct_product.map(x => x.product))]
var disType = [...new Set(this.distinct_product.map(x => x.type))]
var disAttribute = [...new Set(this.distinct_product.map(x => x.attribute))]
this.distinct_product.productArray = disProduct;
this.distinct_product.typeArray = disType;
this.distinct_product.attributeArray = disAttribute;
My problem is, it also renders elements that aren't available to certain products.
for example a product : 'Window' can't have the attribute : 'triple locks'
I was wondering if I could filter/map the all_products array as the user selects a product.
I looked into computed properties mainly but I'm not sure of a good way to do it. this is my first attempt at a web app and I'm fairly new to JS too.
I aimed to iterate through the array pushing only objects containing the product selected in the UI
atm this is what I've attempted with no luck:
this.distinct_product.product which is bound to the UI
for (var i = 0; i < this.all_products.length; i++){
if (this.all_products[i] === this.distinct_product.product){
this.product.push(i);
return this.product;
}
}
so it would iterate over all_products looking for objects containing this.distinct_product.product which would contain 'Protec' or another product
Am I going at this the wrong way? should I step back in general and try and work with that data a different way?
Sorry if the question is structured poorly it's a skill I'm trying to work on, criticism is welcomed.
You are on the right track. I'll share a simple example so you can understand and make changes to your code accordingly.
var productdata = [
{
"product": "Protec",
"type": "Central Opening",
"attribute": "Triple Lock",
"height": "2100",
"width": "1600",
"price": "3000"
},
{
"product": "Protec",
"type": "Sliding Door",
"attribute": "Single Lock",
"height": "2100",
"width": "1600",
"price": "3000"
},
{
"product": "ForceField",
"type": "Hinge Door",
"attribute": "Triple Lock",
"height": "2300",
"width": "1200",
"price": "100"
},
];
//setTimeout(function () {
distinct_productVue = new Vue({
el: '#distinct',
data: {
//selected: {},
distinct_products: [],
all_products: productdata.map(function (x, index) {
return { text: x.product, value: index + 1 };
}),
selected: '0'
},
computed: {
},
mounted: function () {
this.all_products.unshift({ text: 'Please select a product', value: 0 });
},
methods: {
getDistinctProduct: function () {
var self = this;
self.distinct_products = productdata.filter(function (x, index) {
if (x.product === self.all_products[self.selected].text) {
return { text: x.product, value: index };
}
else { return false; }
});
}
}
});
<html>
<head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.0/vue.min.js'></script>
</head>
<body>
<div id="distinct">
<select v-model="selected" v-on:change="getDistinctProduct">
<option v-for="option in all_products" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<!--<span>Selected: {{ selected }}</span>-->
<div v-show="selected != 0" style="margin-top:15px;">
<b>Available products</b>
<div v-for="pro in distinct_products" style="margin-top:15px;">
<div>product: {{pro.product}}</div>
<div>type: {{pro.type}}</div>
<div>attribute: {{pro.attribute}}</div>
<div>height: {{pro.height}}</div>
<div>width: {{pro.width}}</div>
<div>price: {{pro.price}}</div>
</div>
</div>
</div>
</body>
</html>
There is a page where you can see the details of a user's loan. There is a decorator where I return values using the get () method. In general, there is a partial refund, which returns items of partial payments as shown in the photo. My problem is that I cannot specify all partial payments, only receive one by one.
Loan Details component:
<div className="ClientLoanDetails__card__content__inner__wrapper">
{Object.keys(payments[0]).map(val => {
{
[payments[0][val]].map((payment: any, index: number) => (
<div className="ClientLoanDetails__card__content-inner" key={index}>
{paymentsFields.map((item, indexInner) => (
<div className="ClientLoanDetails__card__content-item" key={indexInner}>
<div className="ClientLoanDetails__card__content-item__title">
{item.title}
</div>
<div className="ClientLoanDetails__card__content-item__value">
{payment[item.key]}
</div>
</div>
))}
</div>
));
}}
)
}}
</div>
This is code snippet for key & titles from loan.ts:
export const repaymentsFields = [
{
key: 'issuedDate',
title: lang.CLIENTS.REPAYMENTS.ISSUED_DATE,
},
{
key: 'period',
title: lang.CLIENTS.REPAYMENTS.PERIOD_IN_DAYS,
},
]
JSON of repayments:
"partialRepayments": [
{
"orderId": "A11Fz090VT1BmObJ0S-0",
"repaidPrincipalAmount": {
"amount": 250000.0
},
"repaidInterestAmount": {
"amount": 0
},
"repaidOverdueAmount": {
"amount": 0
},
"repaidProlongationAmount": {
"amount": 0
},
"started": "2020-11-09T16:52:08.981+0600",
"completed": "2020-11-09T16:52:21.170+0600",
"period": 25,
"timestamp": "2020-11-09T16:52:21.174+0600"
},
{
"orderId": "A11Fz090VT1BmObJ0S-1",
"repaidPrincipalAmount": {
"amount": 300000.0
},
"repaidInterestAmount": {
"amount": 0
},
"repaidOverdueAmount": {
"amount": 0
},
"repaidProlongationAmount": {
"amount": 0
},
"started": "2020-11-09T16:54:31.923+0600",
"completed": "2020-11-09T16:54:46.313+0600",
"period": 25,
"timestamp": "2020-11-09T16:54:46.317+0600"
}
],
the problem is that it is impossible to display the values that come as in the photo (one loan may have several repayments)
I have to return all values from an Object
IMAGE of console
Why don't you loop through the partialPayementResponse and access each field through the dot . notation ? Such as follows:
<div>
{partialRepayments.map((item, index) => {
return (
<div key={index}>
<div>Completed: <span>{item.completed}</span></div>
<div>orderId: <span>{item.orderId}</span></div>
<div>period: <span>{item.orderId}</span></div>
<div>repaidInterestAmount: <span>{item.repaidInterestAmount.amount}</span></div>
<div>repaidOverdueAmount: <span>{item.repaidOverdueAmount.amount}</span></div>
<div>repaidPrincipalAmount: <span>{item.repaidPrincipalAmount.amount}</span></div>
<div>repaidProlongationAmount: <span>{item.repaidProlongationAmount.amount}</span></div>
<div>started: <span>{item.started}</span></div>
<div>timestamp: <span>{item.timestamp}</span></div>
</div>)
})}
</div>
I wrote it without using your classNames but I think you get the idea
Not sure what's the problem here.
Can't u just iterate over these things?
Or create function and then display the thing that function returns...
for example:
displayAppropriateFields = () => {
let returnedArray = [];
returnedArray.push(<div> Some text in the div </div>);
for(let i=0; i<someArray.length; i++) {
returnedArray.push(<span> someArray[i] </span>)
}
return returnedArray;
}
And then display it in render method like so:
this.displayAppropriateFields();
I have a scrolling menu items, and the titles of each item is hardcoded into a const, along side with the id
const list = [
{ name: "category1", id: 0 },
{ name: "category2", id: 1 },
{ name: "category3", id: 2 },
{ name: "category4", id: 3 },
{ name: "category5", id: 4 },
{ name: "category6", id: 5 },
{ name: "category7", id: 6 },
{ name: "category8", id: 7 }
];
I have a json file that contains the category name for each child:
{
"results": [
{
"category": "category1",
"name": {
"title": "mr",
"first": "ernesto",
"last": "roman"
},
"email": "ernesto.roman#example.com",
"id": {
"name": "DNI",
"value": "73164596-W"
},
"picture": {
"large": "https://randomuser.me/api/portraits/men/73.jpg",
"medium": "https://randomuser.me/api/portraits/med/men/73.jpg",
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/73.jpg"
}
},
{
"category": "category2",
"name": {
"title": "mr",
"first": "adalbert",
"last": "bausch"
},
"email": "adalbert.bausch#example.com",
"id": {
"name": "",
"value": null
} etc....
I want to show these categories "category": "category1", as the titles of my menu, I now that I need to start stateless and add them from the JSON, the fetching part from the JSON is done locally in componentDidMount, but I am not sure how can I map them into appearing as menu names to make the menu dynamic, I basically want the same output but from the json not hardcoded. here is a sandbox snippet, would appreciate the help.
https://codesandbox.io/s/2prw4j729p?fontsize=14&moduleview=1
Just convert the JSON output to an object like list with a map function from the results and then set is as MenuItems on the state, which is what you pass to the function on render(). Like that.
import React, { Component } from "react";
import ScrollMenu from "react-horizontal-scrolling-menu";
import "./menu.css";
// One item component
// selected prop will be passed
const MenuItem = ({ text, selected }) => {
return (
<div>
<div className="menu-item">{text}</div>
</div>
);
};
// All items component
// Important! add unique key
export const Menu = list =>
list.map(el => {
const { name, id } = el;
return <MenuItem text={name} key={id} />;
});
const Arrow = ({ text, className }) => {
return <div className={className}>{text}</div>;
};
export class Menucat extends Component {
state = {
selected: "0",
MenuItems: []
};
componentDidMount() {
fetch("menu.json")
.then(res => res.json())
.then(result => {
const items = result.results.map((el, idx) => {
return { name: el.category, id: idx };
});
this.setState({
isLoaded: true,
MenuItems: items
});
});
}
render() {
const { selected, MenuItems } = this.state;
// Create menu from items
const menu = Menu(MenuItems, selected);
return (
<div className="App">
<ScrollMenu
data={menu}
selected={selected}
onSelect={this.onSelect}
alignCenter={true}
tabindex="0"
/>
</div>
);
}
}
export default Menucat;
Cheers!
Looks like you don't have to hard code your category list at all. In your componentDidMount() fetch the json and group the results into separate categories like this:
const json = {
"results": [
{
category: "category1",
name: "Fred"
},
{
category: "category1",
name: "Teddy"
},
{
category: "category2",
name: "Gilbert"
},
{
category: "category3",
name: "Foxy"
},
]
}
const grouped = json.results.reduce((acc, cur) => {
if (!acc.hasOwnProperty(cur.category)) {
acc[cur.category] = []
}
acc[cur.category].push(cur)
return acc;
}, { })
// parent object now has 3 properties, namely category1, category2 and category3
console.log(JSON.stringify(grouped, null, 4))
// each of these properties is an array of bjects of same category
console.log(JSON.stringify(grouped.category1, null, 4))
console.log(JSON.stringify(grouped.category2, null, 4))
console.log(JSON.stringify(grouped.category3, null, 4))
Note that this json has 4 objects in result array, 2 of cat1, and 1 of cat 2 and cat3. You can run this code in a separate file to see how it works. Ofcourse you will be fetching the json object from server. I just set it for demonstration.
Then set teh state:
this.setState({ grouped })
Then in render() you only show the categories that have items like:
const menuBarButtons = Object.keys(this.state.grouped).map((category) => {
/* your jsx here */
return <MenuItem text={category} key={category} onClick={this.onClick} blah={blah}/>
/* or something , it's up to you */
})
I'm assuming you're showing the items based on the currently selected category this.state.selected. So after you have rendered your menu, you would do something like:
const selectedCatItems = this.state.grouped[this.state.selected].map((item) => {
return <YourItem name={item.name} key={item.id} blah={blah} />
})
Then render it:
return (
<div className="app">
<MenuBar blah={blah}>
{menuBarButtons}
</Menubar>
<div for your item showing area>
{selectedCatItems}
</div>
</div>
)
Also, don't forget to change your onClick() so that it sets this.state.selected state properly. I believe you can figure that out yourself.
Hope it helps.
PS: I didn't write a whole copy/paste solution to your problem simply because I'm reluctant to read and understand your UI details and the whole component to component data passing details..