Struggling in construction the tree hierarchy in react - javascript

I wanted to use tree view but I am struggling in construction the tree (hierarchical view of information) in react.
same as https://www.w3schools.com/howto/howto_js_treeview.asp
What I am trying CodeSandBox - https://codesandbox.io/s/unruffled-babbage-9knrz?file=/index.js
JSON - (this is not fixed can be any format)
const data = [
{
title: "Node 1",
childNodes: [
{ title: "Childnode 1.1" },
{
title: "Childnode 1.2",
childNodes: [
{
title: "Childnode 1.2.1",
childNodes: [{ title: "Childnode 1.2.1.1" }]
},
{ title: "Childnode 1.2.2" }
]
}
]
}
];
I have toggle function which expand and vice versa the tree node.
I am struggling in construction the tree in react.
Please guide me.

The first of all you don't need use <Tree/> as the recursive component, instead of it use <Node/>
The second thing in React you don't need to use the querySelector and classList for changing state of a component.
I've implemented my idea in code here
const Tree = () => {
return (
<ul>
{data.map(({ title, childNodes }) => (
<Node key={title} title={title} childNodes={childNodes} />
))}
</ul>
);
};
class Node extends React.Component {
state = {
isOpen: false
};
toggle = () => {
this.setState({
isOpen: !this.state.isOpen
});
};
render() {
const { title, childNodes } = this.props;
const { isOpen } = this.state;
return (
<li>
<span className="caret" onClick={this.toggle}>
{title}
</span>
{childNodes && isOpen && (
<ul>
{childNodes.map(({ title, childNodes }) => (
<Node key={title} title={title} childNodes={childNodes} />
))}
</ul>
)}
</li>
);
}
}
export default Tree

Related

How to send a property from an array of object from a child component to the parent component?

I have App, that is the parent component and I have the Child component:
The Child component gets a props called items so it can be reused depending on the data. It the example there is data, data1 and data2.
The thing is that I want to set a cookie from the parent component, to set the cookie I need the property link from data2, but I am already mapping data2 in the Child component.
What can I do to obtain the value of the property link in the parent component to pass it as an arguement here:
<Child
onClick={() =>
handleUpdate('How can I obtain here the string from link of data2?')
}
items={data2}
/>
This is the whole example code:
import * as React from 'react';
import './style.css';
const data = [
{ title: 'hey', description: 'description' },
{ title: 'hey1', description: 'description' },
{ title: 'hey2', description: 'description' },
];
const data1 = [
{ title: 'hey', description: 'description' },
{ title: 'hey1', description: 'description' },
{ title: 'hey2', description: 'description' },
];
const data2 = [
{ title: 'hey', link: 'link/hey' },
{ title: 'hey1', link: 'link/he1' },
{ title: 'hey2', link: 'link/he2' },
];
export default function App() {
const [, setCookie] = useCookie('example');
const handleUpdate = (cookie) => {
setCookie(null);
setCookie(cookie);
};
return (
<div>
<h2>App - Parent</h2>
<Child items={data} />
<Child items={data1} />
<Child
onClick={() =>
handleUpdate('How can I obtain here the string from link of data2?')
}
items={data2}
/>
</div>
);
}
export function Child({ items }) {
return (
<div>
<h2>Child</h2>
<ul>
{items.map((item) => {
return (
<>
<p>{item.title}</p>
<a href={item.link}>Go to title</a>
</>
);
})}
</ul>
</div>
);
}
Thank you!
If you want to get the link from the Child component you can simply add a link parameter in the callback:
<Child
onClick={(link) => handleUpdate(link)}
items={data2}
/>
Then from the Child you just need to call the onClick prop:
export function Child({ items, onClick }) { // here make sure to add the prop while destructuring
<a href={item.link} onClick={() => onClick(item.link)}>Go to title</a>
The map method doesn't change the array that it is called on, it just returns a new array, do the items array doesn't get affected at all here, so you can just call it normally like so:
return (
<div>
<h2>App - Parent</h2>
<Child items={data} />
<Child items={data1} />
<Child
onClick={() =>
handleUpdate(data2[0].link)
}
items={data2}
/>
</div>
);
Also, your Child component needs to accept the onClick function as a prop like so:
export function Child({ items, handleClick }) {
return (
<div onClick={handleClick}>
<h2>Child</h2>
<ul>
{items.map((item) => {
return (
<>
<p>{item.title}</p>
<a href={item.link}>Go to title</a>
</>
);
})}
</ul>
</div>
);
}

map function not showing elements on screen

i have this part of code the map function did not show any element of the array, if i console.log the variable it shows me the elements but for some reasons i can't show the elements on the screen.
Code
function Solution({list}){
const data = list
console.log(data);
return(
<div>
{
data?.map((item) => {
return (
<div>
<p> {item.title} </p>
</div>
)
})
}
</div>
)
}
export default Solution;
const list = [
{
title: "Home"
},
{
title: "Service",
subItem: ["Clean", "Cook"]
},
{
title: "Kitchen",
subItem: ["Wash", "Dish"]
},
];
Solution({list})
Please, just pass "list" link this.
<Solution list={list}/>
Hope will help you, Thanks)
Check this out
import React from 'react';
function Solution({list}){
const data = list
console.log(list);
return(
<div>
{
data?.map((item) => {
return (
<div key={item.id}>
<p> {item.title} </p>
</div>
)
})
}
</div>
)
}
export function App(props) {
const list = [
{
id:1,
title: "Home"
},
{
id:2,
title: "Service",
subItem: ["Clean", "Cook"]
},
{
id:3,
title: "Kitchen",
subItem: ["Wash", "Dish"]
},
];
return (
<div className='App'>
<Solution list={list} />
</div>
);
}
// Log to console
console.log('Hello console')
Have a unique key prop for each element when you map an array and send list array as props to your Solution component

How do you display a filtered list based on a state created with use state?

I have some code in codesandbox that is made up of 4 divs, 2 with the category "Book" and 2 with the category "Article". Some buttons at the top should trigger if all the divs should be displayed, only the books, or only the articles. All the buttons show every div currently, so the page doesn't change and it looks like the state stays the same
Here is the code which is on the sandbox
App.js
import React, { useState } from "react";
/* import Container from './design/Container' */
import Test from "./Test";
const posts = [
{
title: "React Hooks",
content: "The greatest thing since sliced bread!",
category: "Book"
},
{
title: "Using React Fragments",
content: "Keeping the DOM tree clean!",
category: "Article"
},
{
title: "Angular Hooks",
content: "The greatest thing since sliced bread!",
category: "Book"
},
{
title: "Angular Fragments",
content: "Keeping the DOM tree clean!",
category: "Article"
}
];
export default function App() {
const [productItems, setProductItems] = useState(posts);
function handleButton(e) {
console.log(e.target.value);
if (e.target.value === "All") {
setProductItems(posts);
} else {
setProductItems(
posts.filter((p, i) => <div key={i}>p.category === e.target.value</div>)
);
}
setProductItems(posts);
console.log(productItems);
}
return (
<div>
<Test posts={productItems} handleButton={handleButton} />
</div>
);
}
Test.js
import React from "react";
function Post({ p,title, content, category }) {
return (
<React.Fragment>
<div>
<h3>{p.title}</h3>
<div>{p.content}</div>
<br />
<i>
in <b>{p.category}</b>
</i>
</div>
</React.Fragment>
);
}
export default function Test({handleButton, posts = [] }) {
return (
<React.Fragment>
<div>
<button value="All" onClick={handleButton}>
All
</button>
<button value="Book" onClick={handleButton}>
Book
</button>
<button value="Article" onClick={handleButton}>
Article
</button>
</div>
<div>
{posts.map((p) => {
return <Post key={p.title} p={p} />;
})}
</div>
</React.Fragment>
);
}
style.scss
.App {
font-family: sans-serif;
text-align: center;
}
You had a few things wrong, one was that you handleButton required an argument but you weren't passing one to it. You need to call it like onClick={(e) => handleButton(e)} another was that you set the state of product items again after your if statement. You had already set it to the filtered value, but then you overwrote it with the unfiltered value like setProductItems(posts); so you have to remove this line. Another was that your filter function didn't really make sense. I would look it up and learn more about it. It takes a function that returns a boolean; it doesn't return a div.
SOLUTION
(sandbox)
App.js
import React, { useState } from "react";
import Test from "./Test";
const posts = [
{
title: "React Hooks",
content: "The greatest thing since sliced bread!",
category: "Book"
},
{
title: "Using React Fragments",
content: "Keeping the DOM tree clean!",
category: "Article"
},
{
title: "Angular Hooks",
content: "The greatest thing since sliced bread!",
category: "Book"
},
{
title: "Angular Fragments",
content: "Keeping the DOM tree clean!",
category: "Article"
}
];
export default function App() {
const [productItems, setProductItems] = useState(posts);
function handleButton(e) {
console.log(e.target.value);
if (e.target.value === "All") {
setProductItems(posts);
} else {
setProductItems(posts.filter((p) => p.category === e.target.value));
}
console.log(productItems);
}
return (
<div>
<Test posts={productItems} handleButton={handleButton} />
</div>
);
}
Test.js
import React from "react";
const Post = ({ pa }) => {
return (
<React.Fragment>
<div>
<h3>{pa.title}</h3>
<div>{pa.content}</div>
<i>
in <b>{pa.category}</b>
</i>
</div>
</React.Fragment>
);
};
export default ({ posts = [], handleButton }) => (
<>
<div>
<button value="All" onClick={(e) => handleButton(e)}>
All
</button>
<button value="Book" onClick={(e) => handleButton(e)}>
Book
</button>
<button value="Article" onClick={(e) => handleButton(e)}>
Article
</button>
</div>
<div>
{posts.map((pa, i) => (
<Post key={i} pa={pa} />
))}
</div>
</>
);

How to manage a state 'collapsed' for each deeply nested node in a tree

I have a complicated and dynamic data structure like so:
const tree = [
{
name: "Root Node",
collapsed: true,
nodes: [
{
name: "Node 1",
collapsed: true,
nodes: [
{
name: "Sub node"
}
]
},
{
name: "Node 2",
collapsed: true,
nodes: [
{
name: "Sub node "
}
]
},
{
name: "Node 3",
collapsed: true,
nodes: [
{
name: "Sub node"
}
]
}
]
}
];`
I am setting this as the initial state of my component.
I am then rendering this state out as a hierarchy tree in the UI.
When I click the top level node I want to update the collapsed property in state to have it open up and show the next set of nodes.
The problem I am having is how do I call setState() and update this complicated data structure without causing mutation and doing some ugly stuff like tree[0].nodes[0].collapsed: false.
So I first tried setting state like this
handleClick(el, event) {
this.setState({
tree: this.findAndUpdateState(event.target.id).bind(this)
});
}
So on the handleClick event of the node I call this which calls findAndUpdateState.
findAndUpdateState(id) {
this.state.tree.map((node) => {
//Map over the nodes somehow and find the node that needs its state updated using the ID?
});
});
}
I want to update the collapsed property of a node that has been attempted to be expanded. But to do so I have to loop through all the state and then create a new copy of the state just to update that one property. There must a nicer and simpler way that I am not thinking of.
React is pretty good at doing these sort of things.
I'm using the the Hooks in React, as I like them.. :)
Here is a working snippet below..
const tree = [{"name":"Root Node","collapsed":true,"nodes":[{"name":"Node 1","collapsed":true,"nodes":[{"name":"Sub node"}]},{"name":"Node 2","collapsed":true,"nodes":[{"name":"Sub node "}]},{"name":"Node 3","collapsed":true,"nodes":[{"name":"Sub node"}]}]}];
const {useState} = React;
function TreeItem(props) {
const {item} = props;
const [collapsed, setCollapsed] = useState(item.collapsed);
return <div className="item">
<span onClick={() => setCollapsed(!collapsed)}>{item.name}</span>
{!collapsed && item.nodes &&
<div style={{paddingLeft: "1rem"}}>
<TreeList list={item.nodes}/>
</div>
}
</div>
}
function TreeList(props) {
const {list} = props;
return <div>{list.map(f => <TreeItem key={f.name} item={f}/>)}</div>;
}
ReactDOM.render(<TreeList list={tree}/>, document.querySelector('#mount'));
.item {
cursor: pointer;
user-select: none;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="mount"></div>
You could create a component Node with its own state collapsed:
class Node extends Component {
state = {
collapsed: true,
}
toggle = () => {
this.setState(prevState => ({ collapsed: !prevState.collapsed }))
}
render() {
return (
<div>
<p onClick={this.toggle}>{this.props.node.name}</p>
{!this.state.collapsed && (
<div>{this.props.children}</div>
)}
</div>
)
}
}
And create a parent component Tree that renders all the nodes recursively:
const TREE = [{ name: "Root Node", nodes: [...] }]
class Tree extends Component {
renderNodesRecursively = parent => {
return (
<Node node={parent} key={parent.name}>
{parent.nodes
? parent.nodes.map(node => this.renderNodesRecursively(node))
: null
}
</Node>
)
}
render() {
return TREE.map(node => this.renderNodesRecursively(node))
}
}
You will notice that the state of the children is lost each time you toggle the parent. It's "normal" since toggling the parent mounts/unmounts the children. If you want to avoid that, you can replace this code
{!this.state.collapsed && (
<div>{this.props.children}</div>
)}
with
<div style={{ display: this.state.collapsed ? 'none' : 'block' }}>
{this.props.children}
</div>

How to render an array of objects in React?

could you please tell me how to render a list in react js.
I do like this
https://plnkr.co/edit/X9Ov5roJtTSk9YhqYUdp?p=preview
class First extends React.Component {
constructor (props){
super(props);
}
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>;
return (
<div>
hello
</div>
);
}
}
You can do it in two ways:
First:
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>);
return (
<div>
{listItems }
</div>
);
}
Second: Directly write the map function in the return
render() {
const data =[{"name":"test1"},{"name":"test2"}];
return (
<div>
{data.map(function(d, idx){
return (<li key={idx}>{d.name}</li>)
})}
</div>
);
}
https://facebook.github.io/react/docs/jsx-in-depth.html#javascript-expressions
You can pass any JavaScript expression as children, by enclosing it within {}. For example, these expressions are equivalent:
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
This is often useful for rendering a list of JSX expressions of arbitrary length. For example, this renders an HTML list:
function Item(props) {
return <li>{props.message}</li>;
}
function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
<ul>
{todos.map((message) => <Item key={message} message={message} />)}
</ul>
);
}
class First extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [{name: 'bob'}, {name: 'chris'}],
};
}
render() {
return (
<ul>
{this.state.data.map(d => <li key={d.name}>{d.name}</li>)}
</ul>
);
}
}
ReactDOM.render(
<First />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Shubham's answer explains very well. This answer is addition to it as per to avoid some pitfalls and refactoring to a more readable syntax
Pitfall : There is common misconception in rendering array of objects especially if there is an update or delete action performed on data. Use case would be like deleting an item from table row. Sometimes when row which is expected to be deleted, does not get deleted and instead other row gets deleted.
To avoid this, use key prop in root element which is looped over in JSX tree of .map(). Also adding React's Fragment will avoid adding another element in between of ul and li when rendered via calling method.
state = {
userData: [
{ id: '1', name: 'Joe', user_type: 'Developer' },
{ id: '2', name: 'Hill', user_type: 'Designer' }
]
};
deleteUser = id => {
// delete operation to remove item
};
renderItems = () => {
const data = this.state.userData;
const mapRows = data.map((item, index) => (
<Fragment key={item.id}>
<li>
{/* Passing unique value to 'key' prop, eases process for virtual DOM to remove specific element and update HTML tree */}
<span>Name : {item.name}</span>
<span>User Type: {item.user_type}</span>
<button onClick={() => this.deleteUser(item.id)}>
Delete User
</button>
</li>
</Fragment>
));
return mapRows;
};
render() {
return <ul>{this.renderItems()}</ul>;
}
Important : Decision to use which value should we pass to key prop also matters as common way is to use index parameter provided by .map().
TLDR; But there's a drawback to it and avoid it as much as possible and use any unique id from data which is being iterated such as item.id. There's a good article on this - https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
Try this below code in app.js file, easy to understand
function List({}) {
var nameList = [
{ id: "01", firstname: "Rahul", lastname: "Gulati" },
{ id: "02", firstname: "Ronak", lastname: "Gupta" },
{ id: "03", firstname: "Vaishali", lastname: "Kohli" },
{ id: "04", firstname: "Peter", lastname: "Sharma" }
];
const itemList = nameList.map((item) => (
<li>
{item.firstname} {item.lastname}
</li>
));
return (
<div>
<ol style={{ listStyleType: "none" }}>{itemList}</ol>
</div>
);
}
export default function App() {
return (
<div className="App">
<List />
</div>
);
}
import React from 'react';
class RentalHome extends React.Component{
constructor(){
super();
this.state = {
rentals:[{
_id: 1,
title: "Nice Shahghouse Biryani",
city: "Hyderabad",
category: "condo",
image: "http://via.placeholder.com/350x250",
numOfRooms: 4,
shared: true,
description: "Very nice apartment in center of the city.",
dailyPrice: 43
},
{
_id: 2,
title: "Modern apartment in center",
city: "Bangalore",
category: "apartment",
image: "http://via.placeholder.com/350x250",
numOfRooms: 1,
shared: false,
description: "Very nice apartment in center of the city.",
dailyPrice: 11
},
{
_id: 3,
title: "Old house in nature",
city: "Patna",
category: "house",
image: "http://via.placeholder.com/350x250",
numOfRooms: 5,
shared: true,
description: "Very nice apartment in center of the city.",
dailyPrice: 23
}]
}
}
render(){
const {rentals} = this.state;
return(
<div className="card-list">
<div className="container">
<h1 className="page-title">Your Home All Around the World</h1>
<div className="row">
{
rentals.map((rental)=>{
return(
<div key={rental._id} className="col-md-3">
<div className="card bwm-card">
<img
className="card-img-top"
src={rental.image}
alt={rental.title} />
<div className="card-body">
<h6 className="card-subtitle mb-0 text-muted">
{rental.shared} {rental.category} {rental.city}
</h6>
<h5 className="card-title big-font">
{rental.title}
</h5>
<p className="card-text">
${rental.dailyPrice} per Night ยท Free Cancelation
</p>
</div>
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
}
export default RentalHome;
Try this:
class First extends React.Component {
constructor (props){
super(props);
}
render() {
const data =[{"name":"test1"},{"name":"test2"}];
const listItems = data.map((d) => <li key={d.name}>{d.name}</li>;
return (
<div>
{listItems}
</div>
);
}
}

Categories

Resources