React mapping menu from JSON - javascript

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..

Related

How to iterate json object field in react

There is a JSON for categories with the following structure
[
{
"category": "Mobiles",
"sub": [
{
"name": "Apple"
},
{
"name": "Samsung"
}
]
},
{
"category": "Televisions",
"sub": [
{
"name": "Lg"
},
{
"name": "Sony"
}
]
}
]
First i load data from backend to a variable called categories (On the backend side im using expressjs and pass data with res.json(JSON.parse(fs.readFileSync('categories.json')))
I want to iterate through categories sub category with
{categories.map(function (category, i) {
return (
<>
<h6 Key={i}>{category.category}</h6> //For example: <h6>Mobiles</h6>
<>... [logic to iterate the current category's sub categories] ...</> //For example: <p>Apple</p> <p>Samsung</p>
</>
);
})}
I tried to use a second map on category.sub like category.sub.map((s,j)=><p Key={j}>{s.name}</p>) but unfortunely i can't get it work, and I can't describe my problem to Google in English so it can be an easy answer and i am the big L
Any help?
Thanks
Try this, uncomment out the console.log to verify data if screen is white.
return (
<>
{categories.map(function (category, i) {
// console.log(category.category );
// console.log(category.sub );
return (
<>
<h6 key={i}>{category.category}</h6>
<>
{category.sub.map(function (sub, j) {
// console.log(category.category + '' + sub.name);
return <p key={j}> {sub.name}</p>;
})}
</>
</>
);
})}
</>
);
Data:
let categories = [
{
category: 'Mobiles',
sub: [
{
name: 'Apple',
},
{
name: 'Samsung',
},
],
},
{
category: 'Televisions',
sub: [
{
name: 'Lg',
},
{
name: 'Sony',
},
],
},
];

Why v-for & axios not loading items one by one?

I am creating a product web-app by using vue-2.6.11, axios-0.21.1, vuetify-2.4.3
I am fetching categories from local array then I am passing fetchUrl as Props it into Row component by using v-for . Then in Row component i am fetching the fetchUrl by using axios after getting API response I'm simply mounting it. It working fine but the problem is categories object means Row component loads in random order cause the Row component mounted as it got axios response from API.
So I want Next row await till upper fully-mounted or any thing else to make it orderly loaded.
My Components :
Home.vue -
<template>
<div>
<div v-for="(categories,index) in categories" :key="`${index}`">
<ItemsCarousel
:title="categories.title"
:fetch-url="categories.fetchUrl"
/>
</div>
</div>
</template>
<script>
import categoriesList from '#/local-database/Categories.json';
import ItemsCarousel from '#/components/carousel/ItemsCarousel';
export default {
name: 'Home',
components: {
ItemsCarousel
},
data: () => ({
categories: categoriesList.filter( categories => (catalogue.for==true || categories.for=="home"))
})
}
</script>
ItemsCarousel.vue -
<template>
<div class="items-carousel">
<v-lazy v-model="isActive" :options="{threshold: 0.5}">
<h1>{{title}}</h1>
<div class="items-carousel" v-for="product in products" :key="product.id">
<Card v-bind="{...product}">/>
</div>
</v-lazy>
</div>
</template>
<script>
import ProductManger from '#/mixins/ProductManger';
import Card from '#/components/Card';
export default {
name: 'ItemsCarousel',
mixins: [ProductManger], // Axios Setup
components: {
Card
},
props: ['title','params'],
data: () => ({
isActive: false,
cards: []
}),
methods: {
async loadCard() {
this.contentMangerCore(this.params) // Function code inside mixins
.then(res => {
this.cards = res.data;
})
}
},
mounted() {
this.loadCard();
}
};
</script>
DataSample :-
categoriesList.json-
[{
"id": 1,
"name": "Adventure",
"params": {
"categories": "Adventure",
"sort": "ASC"
}
}, {
"id": 2,
"name": "Art",
"params": {
"categories": "Art",
"sort": "DESC"
}
}, {
"id": 3,
"name": "Beauty",
"params": {
"categories": "Art",
"sort": "DESC"
}
}, {
"id": 4,
"name": "Business",
"params": {
"categories": "Art",
"sort": "DESC"
}
}, {
"id": 5,
"name": "Craft",
"params": {
"categories": "Art",
"sort": "DESC"
}
},...]
products.json-
[{
"name": "AdventureIRC",
"img": "..."
},
{
"name": "Adventie",
"img": "..."
},...]
I Hope you guys will help me to resolve this...
Thank You :smile:
You could make a computed method that determines how many categories to actually display at any given time, incremented by successful axios requests.
get categoriesForDisplay() {
return this.categories.slice(0, this.successfulCarouselFetches + 1)
}
Then define successfulCarouselFetches :
data() {
return {
//
successfulCarouselFetches : 0
}
}
listen for successful axios requests in your Item-Carousel component:
<ItemsCarousel
:title="categories.title"
:fetch-url="categories.fetchUrl"
#success="successfulCarouselFetches = successfulCarouselFetches + 1"
/>
Now broadcast the success whenever your xhr is done working:
methods: {
async loadCard() {
this.contentMangerCore(this.params) // Function code inside mixins
.then(res => {
this.cards = res.data;
this.$emit('success');
})
}
},
When the page loads you'll have a single Item-Carousel component on the page which will perform the first XHR request. When the component $emit's the event, the parent component containing the v-for will increment the successfulCarouselFetches which will allow the getter categoriesForDisplay to add another Item-Carousel within the v-for.
This essentially performs what you're asking for, I believe.

Updating State Through props with hooks

I am trying to update the state of something on a click from a component by lifting up the state and passing it as a prop into the other component and trying to update it.
this is the App.js
function App() {
const [currentConfig, setCurrentConfig] = useState(0);
const availableConfigs = [
{ id: 1, name: "Config 1", number: 1, key: 1 },
{ id: 2, name: "Config 2", number: 2, key: 2 },
{ id: 3, name: "Config 3", key: 3 },
{ id: 4, name: "Config 4", key: 4 },
{ id: 5, name: "Config 5", key: 5 },
{ id: 6, name: "Config 6", key: 6 },
{ id: 7, name: "Config 7", key: 7 },
];
const [configs, setConfigs] = useState(availableConfigs);
//function undoConfigAnimation(currentConfig) {}
return (
<div>
<Tree
configs={configs}
animateConfigs={startConfigAnimation}
setConfig={setCurrentConfig}
currentConfig={currentConfig}
/>
<NavBar />
</div>
);
function startConfigAnimation(configClicked) {
console.log(currentConfig);
configs.forEach((config) => {
if (configClicked !== config.name) {
var elm = document.getElementById(config.name);
elm.style.transform = "translate(-200px)";
setTimeout(() => (elm.style.transform = "rotateZ(180deg)"), 1000);
}
});
}
}
export default App;
this is the component
function Tree(props) {
return (
<div class="treeContainer">
{props.configs.map((config) => {
return (
<div
id={config.name}
class="container1"
onClick={() => {
props.setConfig(config.name);
props.animateConfigs(config.name);
if (props.currentConfig !== config.name) {
props.setConfig.bind(config.name);
}
}}
>
<Configs configNumber={config.number} configName={config.name} />
</div>
);
})}
</div>
);
}
export default Tree;
currently, it does update the state, but it only updates it to the state before the click so an example output if the currentConfig === 0 would be as follows
click config 1
currentConfig = 0
click config 2
currentConfig = "config 1"
Since the setState is async, the console.log will always be one behind. This does not mean that the state is not updated, but only not displayed in the console or yet available in the function.
So the flow would be:
You dispatch the change.
You call startConfigAnimation, but it is still in sync, so that currentConfig is still the previous value.
The state is updated with the new value.
There are 2 ways to fix this:
Use a useEffect:
Listen to the currentConfig with a useEffect and trigger the animation, if the config changes.
React.useEffect(() => startConfigAnimation(currentConfig), [currentConfig])
You are already passing the new/updated config to startConfigAnimation so you could be using that.

how to set column name in mui datatables using object key

I'm trying to use an object key to set the name of a column in MUI Datatables.
I'm trying to set one of the column names with the first element of children.childName
so that in that column it will display list of child names, but only the first child.
In Current way that Im trying this, I am getting no errors, and its displaying nothing in the childName Column on the table.
How Can I access an object thats inside an array?
This is my Data:
const data = [
{
name: "Pat",
company: "Test Corp",
city: "Yonkers",
state: "NY",
children: [
{ childName: "Pat Jun", childAge: 2 },
{ childName: "Mary Jun", childAge: 2 }
]
},
];
const columns = [
{
name:name: data[0]["children"][0]["childName"],
label: "Child Name",
options: {
filter: true,
sort: true
}
}]
MuiTable.js
function MuiTable({ forms }) {
console.log("cols", columns);
return (
<MUIDataTable
title={"Title"}
data={data}
columns={columns}
options={options}
/>
);
}
By doing a console.log I can see that it is printing the value instead of the object key name
I would really appreciate any help, Thank you.
You need to use customBodyRender like this:
import React from "react";
import ReactDOM from "react-dom";
import MUIDataTable from "mui-datatables";
import { Typography } from "#material-ui/core";
import "./styles.css";
function App() {
const data = [
{
name: "Pat",
company: "Test Corp",
city: "Yonkers",
state: "NY",
children: [
{ childName: "Pat Jun", childAge: 2 },
{ childName: "Mary Jun", childAge: 2 }
]
}
];
const columns = [
{
name: "children",
label: "Child Names",
options: {
filter: true,
sort: true,
customBodyRender: (value, tableMeta, updateValue) => (
<Typography>
{value.map(child => child.childName).join(",")}
</Typography>
)
}
}
];
return (
<div>
<MUIDataTable title={"Title"} data={data} columns={columns} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Thanks very much to #Klaus your answer. That's pretty much what I had to do, but In my case, I wanted to only display the first childName in the object children which was in an array of objects.
So I had to adapt it a bit and change my data structure as well.
This is what I ended up doing.
I first added a simple array to my data structure completely seperate to the array containing the children objects called childNames, which just came contained only the names.
This made it alot easier to access the childNames as it was just a simple array not nested in anything.
So I simply just displayed the first element in the array on Table
const columns = [
{
name: "childNames",
label: "Child Name",
options: {
filter: true,
customBodyRender: (value, tableMeta, updateValue) => {
return <div>{value[0]}</div>;
}
}
},
The reason why I created an array for just childNames, was because trying to access only just the first childName in the array containing children objects was proving very complicated and difficult.
Thanks very much for all the help.

Make a Tree view from JSON data using React JS

First of all i am very new to React JS. So that i am writing this question. I am trying this for three days.
What I have to do, make a list of category, like-
Category1
->Sub-Category1
->Sub-Category2
Categroy2
Category3
.
.
.
CategoryN
And I have this json data to make the listing
[
{
Id: 1,
Name: "Category1",
ParentId: 0,
},
{
Id: 5,
Name: "Sub-Category1",
ParentId: 1,
},
{
Id: 23,
Name: "Sub-Category2",
ParentId: 1,
},
{
Id: 50,
Name: "Category2",
ParentId: 0,
},
{
Id: 54,
Name: "Category3",
ParentId: 0,
},
];
I have tried many open source examples, but their json data format is not like mine. so that that are not useful for me. I have build something but that is not like my expected result. Here is my jsfiddle link what i have done.
https://jsfiddle.net/mrahman_cse/6wwan1fn/
Note: Every subcategory will goes under a category depend on "ParentId",If any one have "ParentId":0 then, it is actually a category, not subcategory. please see the JSON
Thanks in advance.
You can use this code jsfiddle
This example allows to add new nested categories, and do nested searching.
code with comments:
var SearchExample = React.createClass({
getInitialState: function() {
return {
searchString: ''
};
},
handleChange: function(e) {
this.setState({
searchString: e.target.value.trim().toLowerCase()
});
},
isMatch(e,searchString){
return e.Name.toLowerCase().match(searchString)
},
nestingSerch(e,searchString){
//recursive searching nesting
return this.isMatch(e,searchString) || (e.subcats.length && e.subcats.some(e=>this.nestingSerch(e,searchString)));
},
renderCat(cat){
//recursive rendering
return (
<li key={cat.Id}> {cat.Name}
{(cat.subcats && cat.subcats.length) ? <ul>{cat.subcats.map(this.renderCat)}</ul>:""}
</li>);
},
render() {
let {items} = this.props;
let {searchString} = this.state;
//filtering cattegories
if (searchString.length) {
items = items.filter(e=>this.nestingSerch(e,searchString))
console.log(items);
};
//nesting, adding to cattegories their subcatigories
items.forEach(e=>e.subcats=items.filter(el=>el.ParentId==e.Id));
//filter root categories
items=items.filter(e=>e.ParentId==0);
//filter root categories
return (
<div>
<input onChange={this.handleChange} placeholder="Type here" type="text" value={this.state.searchString}/>
<ul>{items.map(this.renderCat)}</ul>
</div>
);
}
});

Categories