React loop through nested object - javascript

I'm fetching data from strapi.
The response for my navigation object looks like that (simplified):
[
{
"id":1,
"title":"Home",
"order":1,
"items":[
{
"id":2,
"title":"3D Assets",
"order":1,
"items":[
]
},
{
"id":4,
"title":"3D Plants",
"order":2,
"items":[
]
},
{
"id":3,
"title":"Surfaces",
"order":3,
"items":[
{
"id":5,
"title":"Asphalt",
"order":1,
"items":[
]
}
]
}
]
},
{
"id":6,
"title":"Collections",
"order":2,
"items":[
],
"icon":""
}
]
Actually I'm looping through my navigation like that:
{Object.entries(navigationItems).map(([key, value]) => {
return(
<div className="nav_item">
<div className="nav_item_parent">{value.title}
{Object.entries(value.items).map(([key, value]) => {
return(
<div className="nav_item_child">{value.title}
{Object.entries(value.items).map(([key, value]) => {
return(
<div className="nav_item_child">{value.title}</div>
)
})}
</div>
)
})}
</div>
</div>
)
})}
How can I create a navigation without repeating the code for each child? (Because the object could be nested many times)

Here just placing some demo code , please have reference and implement as per your need
Parent Component
import React, {Children} from 'react';
function recursionExample(props) {
let data = [
{
id: 1,
title: 'Home',
order: 1,
items: [
{
id: 2,
title: '3D Assets',
order: 1,
items: [],
},
{
id: 4,
title: '3D Plants',
order: 2,
items: [],
},
{
id: 3,
title: 'Surfaces',
order: 3,
items: [
{
id: 5,
title: 'Asphalt',
order: 1,
items: [],
},
],
},
],
},
{
id: 6,
title: 'Collections',
order: 2,
items: [],
icon: '',
},
];
return (
<div>
{data.map((item, index) => {
return (
<>
<div>{item.title}</div>
{item.items && <ChildrenCom data={item.items}></ChildrenCom>}
</>
);
})}
</div>
);
}
export default recursionExample;
Now below component will call till last-child , as it is called recursively
import React from 'react';
function ChildrenCom(props) {
let {data} = props;
return (
<div>
{data.map((item, index) => {
return (
<>
<div>{item.title}</div>
{item.items && <ChildrenCom data={item.items}></ChildrenCom>}
</>
);
})}
</div>
);
}
export default ChildrenCom;

We could use Depth First Traversal to help us avoid duplication. If you're not comfortable with Depth First Traversal or Recursion, I would recommend you to go through the following snippet initially.
function dfs(item, depth = 0) {
if (!item || Object.keys(item).length === 0) return;
console.log("\t".repeat(depth), item.title);
for (const subItem of item.items) {
dfs(subItem, depth + 1);
}
}
// Consider payload to be the response that you get from the API.
for (const item of payload) {
dfs(item)
}
Once you're comfortable, you could translate it into React.
const Nav = ({ item, depth = 0 }) => {
if (!item || Object.keys(item).length === 0) return;
return (
<>
<p style={{ paddingLeft: `${depth * 64}px` }}>{item.title}</p>
{item.items.map((subItem, index) => (
<Nav item={subItem} depth={depth + 1} />
))}
</>
);
};
export default function App() {
return (
<div className="App">
{payload.map((item) => (
<Nav item={item} />
))}
</div>
);
}

Just a simple recursive tree walk. A component like this:
const NodePropTypes = PropTypes.objectWithShape({
id: PropTypes.number,
title: PropTypes.string,
items: PropTypes.array,
});
const NavListPropTypes = {
nodes: PropTypes.arrayOf( NodePropTypes ),
};
function NavList( props ) {
const nodes = props?.nodes ?? [];
if (nav.length) {
return (
<list>
<ListItems nodes={nodes} />
</list>
);
}
}
NavList.propTypes = NavListPropTypes
function ListItems( props ) {
const nodes = props?.nodes ?? [];
return (
<>
{ nodes.map( node => <ListItem node={node} /> ) }
</>
);
}
ListItems.propTypes = NavListPropTypes;
function ListItem( props ) {
const node = props?.node ?? {};
return (
<li id={node.id} >
<p> {node.title} </p>
<NavList nodes={node.items} />
</li>
);
}
ListItem.propTypes = NodePropTypes;
which can be rendered passing your navigation response:
<NavList nodes={navigationResponse} />
And should yield something like this:
<list>
<li id="1" >
<p> Home </p>
<list>
<li id="2" >
<p> 3D Assets </p>
</li>
<li id="4" >
<p> 3d Plants </p>
</li>
<li id="3" >
<p> Surfaces </p>
<list>
<li id="5" >
<p> Asphalt </p>
</li>
</list>
</li>
</list>
</li>
<li id="6" >
<p> Collections </p>
</li>
</list>

Related

I'm making a forum builder using react-beautiful-dnd I have an object who has id and name and content I want only the content to be dragged

Hello dear community I'm working on Form builder using react-beautiful dnd and I'm trying at the first time to drag only the content but on the list the name will be displayed here is the code if someone can help me
import React from "react";
import Review from "./Review";
import { useState, useEffect } from "react";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
const data = [
{
name: "Input",
id: "1",
content: <input type="text" />,
},
{
name: "Button",
id: "2",
content: <button>I'm a button</button>,
},
{
name: "Image",
id: "3",
content: (
<img src="https://static.wikia.nocookie.net/adventuretimewithfinnandjake/images/e/e6/Site-logo.png/revision/latest?cb=20210530110654" />
),
},
{
name: "Select",
id: "4",
content: (
<select>
<option>Op1</option>
<option>Op2</option>
</select>
),
},
];
const reOrder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
function App() {
const [items, setItems] = useState([]);
useEffect(() => {
setItems(data);
}, []);
const onDragEnd = (result) => {
if (!result.destination) {
return;
}
const reOrderedItems = reOrder(
items,
result.source.index,
result.destination.index
);
console.log(reOrder);
setItems(reOrderedItems);
};
return (
<main>
<section className="container">
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="dragdr">
{(provided, snapshot) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<div className="item">
<div>{item.name}</div>
</div>
</div>
)}
</Draggable>
))}
</div>
)}
</Droppable>
</DragDropContext>
</section>
</main>
);
}
export default App;
I 'll be so greatful if someone can change my dragging behaviour from name to content and thank you in advance

How to add and edit list items in a dual list in reactjs?

I am pretty new to React js and trying different ways to make a to-do list to understand it further. I have a parent component that renders two child components. I figured out how to transfer the items between the two lists. How do I add items to the 2 lists separately from the UI? I am not able to figure that out. I need two input textboxes for each list and also should be able to edit the list items. Can anybody please help me?
import React,{useState,useEffect} from 'react'
import { Completed } from './Completed'
import { Pending } from './Pending'
export const Items = () => {
const [items,setItems]=useState([
{
id: 1,
title:'Workout',
status:'Pending'
},
{
id: 2,
title:'Read Books',
status:'Pending'
},
{
id: 3,
title:'Cook Pizza',
status:'Pending'
},
{
id: 4,
title:'Pay Bills',
status:'Completed'
},
{
id: 5,
title:' Watch Big Short',
status:'Completed'
},
{
id: 6,
title:' Make nutrition Plan',
status:'Pending'
}
])
const updateStatus=(id,newStatus)=>{
let allItems=items;
allItems=allItems.map(item=>{
if(item.id===id){
console.log('in here')
item.status=newStatus;
}
return item
})
setItems(allItems)
}
return (
<div class="items">
<Pending items={items} setItems={setItems} updateStatus={updateStatus}/>
<Completed items={items} setItems={setItems} updateStatus={updateStatus}/>
</div>
)
}
import React from 'react'
export const Completed = ({items,setItems,updateStatus}) => {
return (
<div className="completed">
<h1>RIGHT</h1>
{
items && items.map(item=>{
if(item && item.status==='Completed')
return <><p className="item" key={item.id}>{item.title} <button className="mark_pending" key={item.id} onClick={()=>{updateStatus(item.id,'Pending')}}> Move Left</button></p></>
})
}
</div>
)
}
import React from 'react'
export const Pending = ({items,setItems,updateStatus}) => {
return (
<div className="pending">
<h1>LEFT</h1>
{
items && items.map(item=>{
if(item && item.status==='Pending')
return <><p className="item" key={item.id}>{item.title} <button className="mark_complete" key={item.id} onClick={()=>{updateStatus(item.id,'Completed')}}>Move Right</button></p></>
})
}
</div>
)
}
What do you mean by "separately from the UI" ?
import React, { useState } from "react";
const initialStatus = "Pending";
const initialData = [
{
id: 1,
title: "Workout",
status: "Pending",
},
{
id: 2,
title: "Read Books",
status: "Pending",
},
{
id: 3,
title: "Cook Pizza",
status: "Pending",
},
{
id: 4,
title: "Pay Bills",
status: "Completed",
},
{
id: 5,
title: " Watch Big Short",
status: "Completed",
},
{
id: 6,
title: " Make nutrition Plan",
status: "Pending",
},
];
const Box = ({ id, title, status, setItems, items }) => {
return (
<button
onClick={() => {
const newItems = [...items];
const index = items.findIndex((v) => v.id == id);
newItems[index].status =
newItems[index].status == initialStatus ? "Completed" : initialStatus;
setItems(newItems);
}}
>
{title}
</button>
);
};
export const Items = () => {
const [items, setItems] = useState(initialData);
return (
<div style={{ display: "flex" }}>
<div style={{ display: "flex", flexDirection: "column" }}>
<h1>LEFT</h1>
{items
.filter((v) => v.status === initialStatus)
.map((props) => (
<Box {...props} key={props.id} setItems={setItems} items={items} />
))}
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<h1>Right</h1>
{items
.filter((v) => v.status !== initialStatus)
.map((props) => (
<Box {...props} key={props.id} setItems={setItems} items={items} />
))}
</div>
</div>
);
};
export default Items;

Issue when both of my state are called (TypeError: Cannot read property 'map' of undefined)

I'm having trouble to show to my localhost both of my states (i have the following message : TypeError: Cannot read property 'map' of undefined).
Skills.js
import React, { Component } from 'react';
import ProgressBar from './ProgressBar';
class Skills extends Component {
state = {
programs: [
{ id: 1, value: 'Opera', xp: 5 },
{ id: 2, value: 'Fols', xp: 3 },
{ id: 3, value: 'Micros', xp: 2 },
],
languages: [
{ id: 1, value: 'French', xp: 5 },
{ id: 2, value: 'English', xp: 5 },
{ id: 3, value: 'Spanish', xp: 1 },
]
}
render() {
let { programs, languages } = this.state;
return (
<div className="programsLanguages">
<ProgressBar
programs={programs}
className="programsDisplay"
title="programs"
/>
{/* <ProgressBar
languages={languages}
title="languages"
className="languagesDisplay"
/> */}
</div>
);
}
}
export default Skills;
TO
ProgressBar.js
import React from 'react';
const ProgressBar = (props) => {
return (
<div className={props.className}>
<h3>{props.title}</h3>
<div className="years">
<span>Years of Experience</span>
<span>1 year</span>
<span>8 years</span>
</div>
<div>
console.log(props)
{props.programs.map((item) => {
let xpYears = 8;
let progressBar = (item.xp / xpYears) * 100 + '%';
return (
<div key={item.id} className="programsList">
<li>{item.value}</li>
<div className="progressBar" style={{ width: progressBar }}></div>
</div>
);
})}
</div>
</div>
);
};
export default ProgressBar;
When i comment
{/* <ProgressBar
languages={languages}
title="languages"
className="languagesDisplay"
/> */}
it works. My localHost is showing my object {programs} and same thing if i comment {programs}(my {languages} is showing) but together, it is impossible.
I hope i was enough specific in my exemple.
Thank you very much.
Regards
That's because you used languages as props here
<ProgressBar
languages={languages}
title="languages"
className="languagesDisplay"
/>;
but you're mapping through programs here:
{props.programs.map((item) => {
let xpYears = 8;
let progressBar = (item.xp / xpYears) * 100 + '%';
return (
<div key={item.id} className="programsList">
<li>{item.value}</li>
<div className="progressBar" style={{ width: progressBar }}></div>
</div>
);
})}
also try to check props.programs before mapping to have length like this:
{props.programs.length && props.programs.map((item) => {
let xpYears = 8;
let progressBar = (item.xp / xpYears) * 100 + '%';
return (
<div key={item.id} className="programsList">
<li>{item.value}</li>
<div className="progressBar" style={{ width: progressBar }}></div>
</div>
);
})}
Use React Hook
And pass the same props this way items={progress} OR items={languages}
Like the title and calssName props
import React, { useState } from 'react';
const ProgressBar = ({ title, items, className }) => {
return (
<div className={className}>
<h3>{title}</h3>
<div className="years">
<span>Years of Experience</span>
<span>1 year</span>
<span>8 years</span>
</div>
<div>
console.log(props)
{items.map((item) => {
let xpYears = 8;
let progressBar = (item.xp / xpYears) * 100 + '%';
return (
<div
key={item.id}
className="programsList"
>
<li>{item.value}</li>
<div
className="progressBar"
style={{ width: progressBar }}
></div>
</div>
);
})}
</div>
</div>
);
};
const initialPrograms = [
{ id: 1, value: 'Opera', xp: 5 },
{ id: 2, value: 'Fols', xp: 3 },
{ id: 3, value: 'Micros', xp: 2 },
],
initialLanguages = [
{ id: 1, value: 'French', xp: 5 },
{ id: 2, value: 'English', xp: 5 },
{ id: 3, value: 'Spanish', xp: 1 },
];
const Skills = () => {
const [programs, setPrograms] = useState(initialPrograms);
const [languages, setLanguage] = useState(initialLanguages);
return (
<div className="programsLanguages">
<ProgressBar
items={programs}
className="programsDisplay"
title="programs"
/>
<ProgressBar
items={languages}
title="languages"
className="languagesDisplay"
/>
</div>
);
};
export default Skills;

React js : change background color for specific item in list of items

i have my variable messageInbox is array of messages that will display by .map as below
{messageInbox
.map((chat ,index)=> (
<Inbox chat={chat} backgroundColor={this.state.backgroundColor} inBox={this.props.inBox} setInBox={this.props.setInBox} tourists={this.state.tourists.filter(x => x.hotel_id == this.context.hotel_id[0])} chats={this.props.chats} key={index} />
))
}
i want to change the background color of the clicked item by the onClick event in my div
class Inbox extends Component
constructor(props) {
super(props);
this.state = {
backgroundColor:'#2e405e'
}
}
render() {
return (
<div className="InboxContainer" style={{backgroundColor:this.state.backgroundColor}}
onClick={ this.setState({backgroundColor:'#111f35'}) } >
<div className="ImageMsg">
<img src="/img/chat/contact.png" className="imgContact" />
</div>
<div className="TextMsg">
{this.props.tourists.map(name => {
return name.id == this.props.chat.sender ?
<p className="nameContact"> {name.nom}</p>
: ""
})}
{/* <p className="nameContact"> {this.props.chat.sender}</p> */}
<p className="msgContact">
{this.props.chat.message}
</p>
</div>
{/* <div className="TimeMsg"> */}
{/* <p>{this.props.chat.time}</p> */}
{/* <ReactSVG
src="/img/chat/notSeen.svg"
className="svgIconSeensend"
/>
</div>
*/}
</div>
);
}
}
Inbox.contextType = UserContext
export default withRouter(Inbox);
but she doesn't detect when i clicked on a new item ,can somebody help me,thank's
I can provide you a simple demo how to do with working example.
you can change as per your requirement. Live demo
Code
class App extends React.Component {
state = {
arr: [
{ id: 1, name: "profile", title: "Profile" },
{ id: 2, name: "recruit", title: "Recruitment" },
{ id: 3, name: "arts", title: "Arts" },
{ id: 4, name: "talents", title: "Talents" },
{ id: 5, name: "affection", title: "Affection" }
],
selected: ""
};
changeColor = id => {
this.setState({ selected: id });
};
render() {
const { selected, arr } = this.state;
return (
<div>
<h2>Click to items</h2>
<ul>
{arr.map(({ name, id, title }) => (
<li key={id}>
<h3 style={{color: selected === id ? "red" : ""}}
onClick={() => this.changeColor(id)}
name={name}>
{title}
</h3>
</li>
))}
</ul>
</div>
);
}
}

React 16 Ternary operator used with button, called with function gives error Maximum update depth exceeded

My goal is when I click on any list items, I want to show hidden button as confirm. What I tried is to show button on-click on function name selectItem as follows :
<Col xs={3}>
<ul>
<h2>Your orders </h2>
{selectedItems.map((item, i) => (
<li key={i}>
{item.name} {item.cost} {item.quantity}
<span onClick={() => this.deleteItem(i)}>cancel</span>
</li>
))}
</ul>
{this.selectItem()
? <Button type="button" style={{ display: 'block' }}>Confrim</Button>
: <Button type="button" style={{ display: 'none' }}>Confrim</Button>
}
</Col>
This gives the error as follows
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
The question is how I use the function call to display hidden button and hide when I remove all Items.Thanks.
import React from "react";
import {
Form,
FormGroup,
Row,
FormControl,
Col,
Button,
Label,
Modal,
ButtonToolbar,
Table
} from "react-bootstrap";
const MorningDrinks = [
{
id: "1",
name: "Tea",
cost: 15
},
{
id: "2",
name: "Coffee",
cost: 15
},
{
id: "3",
name: "Milk",
cost: 15
}
];
const ChoclateDrinks = [
{
id: "4",
name: "Smoothie",
cost: 15
},
{
id: "5",
name: "Hot Chocolate",
cost: 15
}
];
class MenuCard extends React.Component {
state = {
selectedItems: []
};
selectItem = item => {
const { counter, selectedItems } = this.state;
const newItem = {
...item,
quantity: 1
};
const el = selectedItems.filter(el => el.id === newItem.id);
if (selectedItems.length === 0) {
this.setState({
selectedItems: selectedItems.concat([newItem])
});
} else {
if (el.length) {
const newSelectedItems = selectedItems.map(item => {
if (item.id === newItem.id) {
item.quantity++;
}
return item;
});
this.setState({
selectedItems: newSelectedItems
});
} else {
this.setState({
selectedItems: selectedItems.concat([newItem])
});
}
}
};
deleteItem(i) {
this.setState({
selectedItems: this.state.selectedItems.filter((item, index) => {
return index !== i;
})
});
}
render() {
const { counter, selectedItems } = this.state;
return (
<div className="container">
<p>
Welcome {this.props.name}! Pick your any Break-fast menu you want
</p>
<Row>
<Col xs={3}>
<ul>
<h2>Morning Drinks </h2>
{MorningDrinks.map((item, i) => (
<li
style={{ cursor: "pointer" }}
key={i}
onClick={() => this.selectItem(item)}
>
{item.name} {item.cost}
</li>
))}
</ul>
<ul>
<h2>Chocolate Drinks </h2>
{ChoclateDrinks.map((item, i) => (
<li
style={{ cursor: "pointer" }}
key={i}
onClick={() => this.selectItem(item)}
>
{item.name} {item.cost}
</li>
))}
</ul>
</Col>
<Col xs={3}>
<ul>
<h2>Your orders </h2>
{selectedItems.map((item, i) => (
<li key={i}>
{item.name} {item.cost} {item.quantity}
<span onClick={() => this.deleteItem(i)}>cancel</span>
</li>
))}
</ul>
<Button type="button" style={{display: 'none'}}>Confrim</Button>
</Col>
<Col xs={3}>
<ul>
<h3>Total</h3>
{selectedItems.reduce(
(acc, item) => acc + item.cost * item.quantity,
0
)}
</ul>
</Col>
</Row>
</div>
);
}
}
export default MenuCard;
this.selectItem()
Sets state, which can't be done inside a render method. Try to render the button by only reading from state.
{this.state.selectedItems.length > 0 ? ... : ...}

Categories

Resources