Render collection of nested JSON Data - reactjs

I'm using a template file that takes in data from a JSON and displays it as a list
JSON -
"items":[
{
"name":"a",
},
{
"name":"b",
},
{
"name":"c",
}
]
JS File
var items= this.props.data.items.map(function(items){
return <li key={items.name}><span className={items.name}></span><em>{items.name}</em></li>
})
//where it later gets rendered like so:
<div className="four columns main-col">
<div className="bars">
<ul className="items">
{items}
</ul>
</div>
</div>
However I want to modify the data, so that its more categorized, and will have nested objects
Example:
"categorizedItems":[
{
"type":"a",
"items":[
{
"name":"apple"
},
{
"name":"banana"
}
]
},
{
"type":"b",
"items":[
{
"name":"car"
}
]
}
]
So i thought, since it is a nested JSON object, I will need to map twice, so i tried the following:
var categories= this.props.data.categorizedItems.map(function(category){
var items= category.items.map(function(item){
return <li key={items.name}><span className={items.name}></span><em>{items.name}</em></li>
})
return <ul key={category.type}>{items}</ul>
})
//and i render it the same way
<div className="four columns main-col">
<div className="categories">
{categories}
</div>
</div>
However this gives me errors saying "Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead."
I dont understand how what I am doing (the nested mapping) is different from the original code (single mapping).

I resolved the errors in your code
Try this
import React from "react";
import "./styles.css";
const data = [
{
type: "a",
items: [
{
name: "apple"
},
{
name: "banana"
}
]
},
{
type: "b",
items: [
{
name: "car"
}
]
}
];
const categories = data.map((category) => {
const items = category.items.map(function (item) {
return (
<li key={item.name}>
<span className={item.name}></span>
<em>{item.name}</em>
</li>
);
});
return <ul key={category.type}>{items}</ul>;
});
export default function App() {
return (
<div className="four columns main-col">
<div className="categories">{categories}</div>
</div>
);
}
Here is sandbox link - https://codesandbox.io/s/nervous-noether-80ye9?file=/src/App.js:0-714
the problem in existing code.
Instead of
var categories= this.props.data.categorizedItems.map(function(category){
var items= category.items.map(function(item){
return <li key={items.name}><span className={items.name}></span><em>{items.name}</em></li>
})
return <ul key={category.type}>{items}</ul>
})
Try
var categories= this.props.data.categorizedItems.map(function(category){
var items= category.items.map(function(item){
return <li key={item.name}><span className={item.name}></span><em>{item.name}</em></li>
})
return <ul key={category.type}>{items}</ul>
})

Related

combining useState with pop()

I'm trying to update my list using the pop() method.
But the page doesn't render, and I couldn't find the issue.
I would appreciate any help.
this is my Code
import classes from "./Navigation.module.scss";
import { useState } from "react";
function Navigation(props) {
const [navItems, setNavItems] = useState([
{ id: 0, link: "HOME" },
{ id: 1, link: "ABOUT" },
{ id: 2, link: "PORTFOLIO" },
{ id: 3, link: "MUSIC" },
{ id: 4, link: "CONTACT" },
]);
const nextHandler = () => {
let a = navItems;
a.pop();
return setNavItems(a);
};
return (
<div className={classes.wrapper}>
<div onClick={nextHandler} className={classes.nextButton}>
NEXT
<div>
<div className={classes.listWrapper}>
<div
className={classes.container}
>
<ul>
{navItems.map((item) => {
return <li key={item.id}>{item.link}</li>;
})}
</ul>
<div>
<div>
</div>
);
}
export default Navigation;
So basically, I want to remove the last item when I click the button and want to update the list.
Try it like this:
const nextHandler = () => {
let a = [...navItems];
a.pop();
return setNavItems(a);
};
Otherwise you just copy the reference to the array to a and react will not know that the state has changed.

How to map nested array in gatsby with reactjs and graphql

I have a component, menu.js, that i import into a page to produce a list of articles, that can be filtered by category. This works perfectly.
Now i want to change the component so that i can filter the articles by tags. The problem is that the tags are a nested array in graphql, that i cant reach with the same map() function that maps the categories.
I have tried to do a nested map function but i cant get it to work, but i suspect that is the solution. My goal is to have the same functionality where i can filter the articles by tags, instead of by category. I hope thats possible. I am using gatsby, with a Strapi backend. Any hints in the right direction appreciated :-)
/src/pages/articles.js
import graphql from 'gatsby'
import React from 'react'
import Layout from 'components/layout'
import MenuBlog from 'components/menublog'
const BlogPage = ({ data }) => (
<Layout>
<MenuBlog items={data.menu} />
</Layout>
)
export default BlogPage
export const pageQuery = graphql`
query BlogQuery {
menu: allStrapiArticle {
edges {
node {
id
title
slug
tag {
title
id
}
category {
title
id
}
}
}
}
}
`
This is what i get back from the GraphQL query above, each article can of course have one or more tags, but only one category assigned
{
"data": {
"menu": {
"edges": [
{
"node": {
"title": "articleName 1",
"slug": "articleName-1",
"category": {
"title": "cat1"
},
"tag": [
{
"title": "tag1"
},
{
"title": "tag2"
},
{
"title": "tag3"
}
]
}
},
{
"node": {
"title": "articleName 2",
"slug": "articleName-2",
"category": {
"title": "cat2"
},
"tag": [
{
"title": "tag3"
}
]
}
}
]
}
}
}
And here is my component that displays the articles according to the chosen category
/src/components/menublog/index.js
import React, { Component } from 'react'
import { Link } from 'gatsby'
import Row from 'react-bootstrap/Row'
const getCategories = items => {
let tempItems = items.map(items => {
return items.node.category.title
})
let tempCategories = new Set(tempItems)
let categories = Array.from(tempCategories)
categories = ['all', ...categories]
return categories
}
export default class MenuBlog extends Component {
constructor(props) {
super(props)
this.state = {
items: props.items.edges,
articles: props.items.edges,
categories: getCategories(props.items.edges),
}
}
handleItems = category => {
let tempItems = [...this.state.items]
if (category === 'all') {
this.setState(() => {
return { articles: tempItems }
})
} else {
let items = tempItems.filter(
({ node }) => node.category.title === category
)
this.setState(() => {
return { articles: items }
})
}
}
render() {
if (this.state.items.length > 0) {
return (
<Row>
{/* items */}
<div className="col-md-8 blog-main bg-light">
<h1>Artikler</h1>
{this.state.articles.map(({ node }) => {
return (
<div key={node.id} className="blog-post mb-4">
<h2>
<Link to={`/artikler/${node.slug}`}>{node.title}</Link>
</h2>
{/* item text */}
</div>
)
})}
</div>
{/* categories */}
<div className="col-md-4 blog-sidebar">
<div className="p-4 mb-3 bg-light">
<h4>Kategorier</h4>
<ol className="list-unstyled mb-0">
{this.state.categories.map((category, index) => {
return (
<li key={index}>
<button
type="button"
className="btn"
onClick={() => {
this.handleItems(category)
}}
>
{category}
</button>
</li>
)
})}
</ol>
</div>
<div className="p-4 mb-3 bg-light">
<h4>Kategorier</h4>
</div>
</div>
</Row>
)
} else {
return <h1>no items</h1>
}
}
}
You should be able to use something similar to your category method:
items = tempItems.filter(({ node }) =>
node.tag.map(tag => tag.title).includes("tag2")
);
Since this isn't necessarily React / Gatsby specific, here is only the data and these methods:
const data = {
data: {
menu: {
edges: [{
node: {
title: "articleName 1",
slug: "articleName-1",
category: {
title: "cat1"
},
tag: [{
title: "tag1"
},
{
title: "tag2"
},
{
title: "tag3"
}
]
}
},
{
node: {
title: "articleName 2",
slug: "articleName-2",
category: {
title: "cat2"
},
tag: [{
title: "tag3"
}]
}
}
]
}
}
};
let items = data.data.menu.edges.filter(
({
node
}) => node.category.title === "cat2"
);
console.log(items);
items = data.data.menu.edges.filter(({
node
}) =>
node.tag.map(tag => tag.title).includes("tag2")
);
console.log(items);

Create a multidimensional list from Array Objects

I am trying to create a multidimensional list from object full of arrays from a rest request in Javascript. The issue is my ability iterate over an array of objects. Can someone give me an example on how to turn this data structure into a JSX component?
I am trying to create a list that is wrapped in a div and looks like:
<div>
<lo>
<li>
<ul>
<li>Row Cell</li>
<li>Row Cell</li>
</ul>
</li>
<li>
<ul>
<li>Row Cell</li>
<li>Row Cell</li>
</ul>
</li>
</lo>
</div>
The data structure looks like this,
The function that is set in the React Component is the following,
createBodyDisplay(){
var ar = this.state.data.request.body;
var returnString = '';
for (var key in ar) {
console.log(ar);
if (ar.hasOwnProperty(key)) {
if(ar instanceof Array){
console.log('This is a test to see if there is an array');
} else if (ar instanceof Object){
for (var key1 in ar) {
if (ar.hasOwnProperty(key1)) {
console.log(ar[key1]);
}
}
console.log(ar);
} else {
console.log('Not sure what this is');
}
// returnString= returnString+'<div>';
/// var x = numbers.map(Math.sqrt)
// console.log(ar[key]);
// returnString= returnString+'</div>';
}
}
// console.log(returnString);
return returnString;
}
See sandbox here for live example: https://codesandbox.io/s/confident-heyrovsky-s0zg4
Assuming your data-structure looks something like:
const newData = {
dogs: [
{ type: "row-cell", value: "Golden" },
{ type: "row-cell", value: "Husky" }
],
cats: [
{ type: "row-cell", value: "Feline" },
{ type: "row-cell", value: "Hairless" }
]
};
We can use Object.entries() to cleanly create an array of arrays, for each key-value pair. Then use .map() to create our outer-ordered-list items. And within each group, we will use another .map() to create the unordered-list-items.
Working code:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
data: {}
};
componentDidMount() {
const newData = {
dogs: [
{ type: "row-cell", value: "Golden" },
{ type: "row-cell", value: "Husky" }
],
cats: [
{ type: "row-cell", value: "Feline" },
{ type: "row-cell", value: "Hairless" }
]
};
this.setState({
data: newData
});
}
createNestedLists = () => {
const { data } = this.state;
const lists = Object.entries(data).map(([type, arr]) => {
return (
<li>
<ul>
{arr.map(item => {
return (
<li>
{item.type} - {item.value}
</li>
);
})}
</ul>
</li>
);
});
return <ol>{lists}</ol>;
};
render() {
return <div>{this.createNestedLists()}</div>;
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

groupBy json data then map in React component

I'm trying to group my JSON data so that all staff in the same 'department' are in an object together. Then I want to map through the data so I can display it on my webpage.
At the moment i'm getting the error 'cannot read property of map undefined'
my JSON data:
people:[
{
id:"a0bef",
title:"cleaner",
department:"facilities",
},
{
id:"a0beg",
title:"maintenance",
department:"facilities",
},
{
id:"a0beh",
title:"cleaner",
department:"facilities",
},
{
id:"a0bei",
title:"chef",
department:"kitchen",
},
{
id:"a0bej",
title:"waitress",
department:"kitchen",
}
]
which I would like to look like:
people:[
"facilities":[
{
id:"a0bef",
title:"cleaner"
},
{
id:"a0beg",
title:"maintenance"
},
{
id:"a0beh",
title:"cleaner"
}
],
"kitchen":[
{
id:"a0bei",
title:"chef"
},
{
id:"a0bej",
title:"waitress"
}
]
]
this is what I have tried:
import React from 'react'
import _ from 'lodash'
class PeopleList extends React.Component {
constructor (props) {
super(props)
}
render () {
const { peoplelist } = this.props
const people = _.groupBy(peoplelist, 'department')
return (
<div>
{ Object.people.map(function (person, key) {
return (
<div className='row' key={key}>
<div className='col-md-6'>{person.department}</div>
<div className='col-md-4'>{person.title}</div>
</div>
)
}) }
</div>
)
}
}
export default PeopleList
You are getting this error because Object.people is not a valid syntax.
people after const people = _.groupBy(peoplelist, 'department') will be an Object. You need to get all the values of the object (using Object.values(people)); which will give you an array of person. Then, map through that array to get the desired output.
The function will be modified to
Object.values(people).map(function (deptArray) {
return deptArray.map(function (person) {
return (
<div className='row' key={some-unique-key}>
<div className='col-md-6'>{person.department}</div>
<div className='col-md-4'>{person.title}</div>
</div>
)
})
})
Note: In the above case you won't be able to use array indexes as the key, because the keys will be repeated (and so, I have removed it from my solution).
Hope it helps. Revert for any clarifications/doubts.
At the moment i'm getting the error 'cannot read property of map
undefined'
This is due to this line in your return statement { Object.people.map(function (person, key)
const people = _.groupBy(peoplelist, 'department')
Above will you an object of 2 arrays i.e facilities and kitchen.
So you can do the following for displaying the data.
Object.values(people).map(dept => {
dept.map((person, key) => {
return (
<div className='row' key={key}>
<div className='col-md-6'>{person.department}</div>
<div className='col-md-4'>{person.title}</div>
</div>
)
})
})
If you want to group your items by department and then display a list of persons ordered by department, then you need to map twice. Once over the departments, and then once over each person in each department.
To iterate over the departments, you will need to use Object.values() or Object.entries().
Here is an example using reduce() to implement groupBy:
const people = [
{ id: "a0bef", title: "cleaner", department: "facilities" },
{ id: "a0beg", title: "maintenance", department: "facilities" },
{ id: "a0beh", title: "cleaner", department: "facilities" },
{ id: "a0bei", title: "chef", department: "kitchen" },
{ id: "a0bej", title: "waitress", department: "kitchen" }
]
function groupBy(data, key) {
return data.reduce((acc, x) => {
acc[x[key]] = [...(acc[x[key]] || []), x];
return acc;
}, {});
}
class PeopleList extends React.Component {
constructor(props) {
super(props)
}
render() {
const people = groupBy(this.props.people, 'department')
return (
<div className='container'>
{Object.entries(people).map(([dep, staff]) => {
return (
<div className='dep' key={dep}>
<span className='dep-name'>{dep}</span>
{staff.map(person => {
return (
<div className='row' key={person.id}>
<div className='col-xs-6'>Dep: {person.department}</div>
<div className='col-xs-4'>Title: {person.title}</div>
</div>
)
})}
</div>
)
})}
</div>
)
}
}
ReactDOM.render(
<PeopleList people={people} />,
document.getElementById('root')
);
.dep {
border: 1px solid black;
margin: 5px;
padding: 5px;
}
.dep-name {
font-weight: bold;
}
<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>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<div id="root"></div>

ReactJS map through an array with objects

I have that part of code
const links = [
{
name: 'How it works',
ref: '/'
},
{
name: 'Calendar',
ref: 'calendar'
},
{
name: 'Contact me',
ref: 'contact'
}
];
const renderLinks = links.map((link, index) =>
<li className="nav-item active" key={index}>
<a className="nav-link" href={link.ref || "#"}>
{link.name}
</a>
</li>
);
However when I try to render it an error is thrown.
render() {
return (
{renderLinks}
);
}
Objects are not valid as a React child (found: object with keys
{renderLinks}). If you meant to render a collection of children, use
an array instead.
As I think, I have to got an array but React thinks there is an object.
React thinks this is an object because you indeed provided an object. This is what it is if you write it without shortcut property notation:
render() {
return {
renderLinks: renderLinks
);
}
Just return renderLinks directly without { }:
render() {
return renderLinks;
}

Resources