How would i go about rendering a menu with nested <ul> items with an an unknown amount of children in react from an object like in the following example?
[
{
title: "Top level 1",
slug: "top-level-1",
children: [
{
title: "Sub level 1",
slug: "sub-level-1",
children: [
{
title: "Sub Sub Level 1"
slug: "sub-sub-level-1"
}
]
}
{
title: "Sub level 2",
slug: "sub-level-2"
}
]
},
{
title: "Top level 2",
slug: "top-level 2"
}
]
Codesandbox example
You just have to recursively call Menu component for its children to display and pass as a data prop.
let data = [
{
title: "Top level 1",
slug: "top-level-1",
children: [
{
title: "Sub level 1",
slug: "sub-level-1",
children: [
{
title: "Sub Sub Level 1",
slug: "sub-sub-level-1",
children: [
{
title: "Sub Sub Level 2",
slug: "sub-sub-level-2"
}
]
}
]
},
{
title: "Sub level 2",
slug: "sub-level-2"
}
]
},
{
title: "Top level 2",
slug: "top-level 2"
}
];
const Menu = ({data}) => {
return (
<ul>
{data.map(m => {
return (<li>
{m.title}
{m.children && <Menu data={m.children} />}
</li>);
})}
</ul>
);
}
const App = () => (
<div style={styles}>
<Hello name="CodeSandbox" />
<h2>Start editing to see some magic happen {'\u2728'}</h2>
<Menu data={data} />
</div>
);
You could recursively Render the component for nested data which has variable depth.
Sample Snippet.
var data = [
{
title: "Top level 1",
slug: "top-level-1",
children: [
{
title: "Sub level 1",
slug: "sub-level-1",
children: [
{
title: "Sub Sub Level 1",
slug: "sub-sub-level-1"
}
]
},
{
title: "Sub level 2",
slug: "sub-level-2"
}
]
},
{
title: "Top level 2",
slug: "top-level 2"
}
]
const MyComponent = (props) => {
if(Array.isArray(props.collection)) {
return <ul>
{props.collection.map((data)=> {
return <li>
<ul>
<li>{data.title}</li>
<li>{data.slug}</li>
<li><MyComponent collection={data.children}/></li>
</ul>
</li>
})
}
</ul>
}
return null;
}
class App extends React.Component {
render() {
return (
<MyComponent collection={data}/>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'))
<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="app"></div>
P.S. The snippet contains formatting errors, but I am sure you will be able to rectify that. Snippet was to give an idea of the approach
Related
I would like to generate a tree from the following object:
configuration:
{
name: "General",
icon: "general",
children: [
{
name: "Line 1",
icon: "line",
children: [
{
name: "Area 1",
icon: "area",
children: [
{ name: "Y", icon: "x" },
{ name: "Z", icon: "z" },
],
},
{ name: "Area 2", icon: "line" },
],
},
{
name: "Line 2,
icon: "line",
children: [{ name: "Area 3", icon: "area" }],
},
],
},
In html I have my own custom element:
<my-details>
<div slot="summary"><my-icon name="${icon}"></my-icon> ${name}</div>
${this.children} // ???
</my-details>`;
so I created the function:
createHierarchy() {
if (configuration?.length > 0) {
const details = _configuration.map(({ name, icon }) => {
return ` <my-details>
<div slot="summary"><my-icon name="${icon}"></my-icon> ${name}</div>
${this.children}
</my-details>`;
});
hierarchyContainer.innerHTML = details?.join("");
}
}
but I don't know how to convert this structure in a loop or map to a tree, each element of the hierarchy should be a my-details element and have a name as a slot and in the middle it should have children
the structure should look like this:
hierarchy tree
What you are looking for is a recursive function. Which is a function that calls itself.
It should end up looking something like this:
function parseNode(node) {
let html = `<my-details>
<div slot="summary">
<my-icon name="${node.icon}"></my-icon>
${node.name}.
</div>`;
if(node.children) {
node.children.forEach(childNode => {
html += parseNode(childNode);
})
;}
html += '</my-details>';
return html;
}
parseNode(configuration);
This example turns your entire configuration into an html string (or it should it is untested). You can output to your document/component.
Note that it relies on all nodes having a name and an icon and a possible "children" has to be an array.
I am trying to integrate React-Beautiful-DND and Ant Design table, but I am facing an issue with rows as I drag them. Their style is changing and the whole table jumps. Anyone tried this before? How do I keep the style of dragged row as it was when it was not dragged?
Any ideas will be much appreciated.
Here is a link to full sandbox.
You can see that if you drag any row, the table will jump.
An here is a full code from the link:
import React, { useState } from "react";
import { Table, Row, Col, Card, Empty } from "antd";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";
import { mutliDragAwareReorder } from "./utils";
import "./style.css";
const entitiesMock = {
tasks: [
{ id: "0", title: "Very long task title" },
{ id: "1", title: "Task 1" },
{ id: "2", title: "Task 2" },
{ id: "3", title: "Task 3" },
{ id: "4", title: "Task 4" },
{ id: "5", title: "Task 5" },
{ id: "6", title: "Task 6" },
{ id: "7", title: "Task 7" },
{ id: "8", title: "Task 8" },
{ id: "9", title: "Task 9" },
{ id: "10", title: "Task 10" },
{ id: "11", title: "Task 11" },
{ id: "12", title: "Task 12" },
{ id: "13", title: "Task 13" },
{ id: "14", title: "Task 14" },
{ id: "15", title: "Task 15" },
{ id: "16", title: "Task 16" },
{ id: "17", title: "Task 17" },
{ id: "18", title: "Task 18" },
{ id: "19", title: "Task 19" }
],
columnIds: ["todo"],
columns: {
todo: {
id: "todo",
title: "To do",
taskIds: [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19"
]
},
done: {
id: "done",
title: "Done",
taskIds: []
}
}
};
const COLUMN_ID_DONE = "done";
export const MultiTableDrag = () => {
const [entities, setEntities] = useState(entitiesMock);
const [selectedTaskIds, setSelectedTaskIds] = useState([]);
const [draggingTaskId, setDraggingTaskId] = useState(null);
const tableColumns = [
{
title: "ID",
dataIndex: "id"
},
{
title: "Title",
dataIndex: "title"
}
];
/**
* Droppable table body
*/
const DroppableTableBody = ({ columnId, tasks, ...props }) => {
return (
<Droppable droppableId={columnId}>
{(provided, snapshot) => (
<tbody
ref={provided.innerRef}
{...props}
{...provided.droppableProps}
className={`${props.className} ${
snapshot.isDraggingOver && columnId === COLUMN_ID_DONE
? "is-dragging-over"
: ""
}`}
>
{props.children}
{provided.placeholder}
</tbody>
)}
</Droppable>
);
};
/**
* Draggable table row
*/
const DraggableTableRow = ({ index, record, columnId, tasks, ...props }) => {
if (!tasks.length) {
return (
<tr className="ant-table-placeholder row-item" {...props}>
<td colSpan={tableColumns.length} className="ant-table-cell">
<div className="ant-empty ant-empty-normal">
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div>
</td>
</tr>
);
}
const isSelected = selectedTaskIds.some(
(selectedTaskId) => selectedTaskId === record.id
);
const isGhosting =
isSelected && Boolean(draggingTaskId) && draggingTaskId !== record.id;
return (
<Draggable
key={props["data-row-key"]}
draggableId={props["data-row-key"].toString()}
index={index}
>
{(provided, snapshot) => {
return (
<tr
ref={provided.innerRef}
{...props}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`row-item ${isSelected ? "row-selected" : ""} ${
isGhosting ? "row-ghosting" : ""
} ${snapshot.isDragging ? "row-dragging" : ""}`}
>
{props.children}
</tr>
);
}}
</Draggable>
);
};
/**
* Get tasks
*/
const getTasks = (entities, id) => {
return entities.columns[id].taskIds.map((taskId) =>
entities.tasks.find((item) => item.id === taskId)
);
};
/**
* On before capture
*/
const onBeforeCapture = (start) => {
const draggableId = start.draggableId;
const selected = selectedTaskIds.find((taskId) => taskId === draggableId);
// if dragging an item that is not selected - unselect all items
if (!selected) {
setSelectedTaskIds([]);
}
setDraggingTaskId(draggableId);
};
/**
* On drag end
*/
const onDragEnd = (result) => {
const destination = result.destination;
const source = result.source;
// nothing to do
if (!destination || result.reason === "CANCEL") {
setDraggingTaskId(null);
return;
}
const processed = mutliDragAwareReorder({
entities,
selectedTaskIds,
source,
destination
});
setEntities(processed.entities);
setDraggingTaskId(null);
};
return (
<>
<Card
className={`c-multi-drag-table ${draggingTaskId ? "is-dragging" : ""}`}
>
<div>
selectedTaskIds: {JSON.stringify(selectedTaskIds)}
<br />
draggingTaskId: {JSON.stringify(draggingTaskId)}
</div>
<br />
<DragDropContext
onBeforeCapture={onBeforeCapture}
onDragEnd={onDragEnd}
>
<Row gutter={40}>
{entities.columnIds.map((id) => (
<Col key={id} xs={12}>
<div className="inner-col">
<Row justify="space-between" align="middle">
<h2>{id}</h2>
<span>
{draggingTaskId && selectedTaskIds.length > 0
? selectedTaskIds.length +
" record(s) are being dragged"
: draggingTaskId && selectedTaskIds.length <= 0
? "1 record(s) are being dragged"
: ""}
</span>
</Row>
<Table
dataSource={getTasks(entities, id)}
columns={tableColumns}
rowKey="id"
components={{
body: {
// Custom tbody
wrapper: (val) =>
DroppableTableBody({
columnId: entities.columns[id].id,
tasks: getTasks(entities, id),
...val
}),
// Custom td
row: (val) =>
DraggableTableRow({
tasks: getTasks(entities, id),
...val
})
}
}}
// Set props on per row (td)
onRow={(record, index) => ({
index,
record
})}
/>
</div>
</Col>
))}
</Row>
<br />
</DragDropContext>
</Card>
</>
);
};
I have the component pasted below that outputs a grid of 6 items. These 6 items are currently hardcoded as gridItems.
I would like to understand how I can make this component dynamic. Where I can tell the component, via props, which items (designer, manager, ...) to render.
Currently, the component will output 6 items. How can I make it so by doing something like:
<RoleCardGrid designer manager />
Renders the component with only 2 of the items.
Example 2:
<RoleCardGrid designer manager doctor sales />
Renders the component with only 4 of the items.
Thank you for the help.
Here is my current component:
import React from 'react';
import RoleCard from 'components/RoleCard';
let gridItems = [
{ roleTitle: "designer",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle: "manager",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle:"freelancer",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle:"engineer",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle:"doctor",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle:"sales",
linkTo: "example.com",
description: "Desc goes here",
}
];
function renderGridItems(items) {
let rendered = items.map((item, index) => {
return (<div key={`gridItem-${index}`}>
<RoleCard
key={`roleGridItem-${index}`}
roleTitle={item.roleTitle}
linkTo={item.linkTo}
description={item.description}
/>
</div>);
});
return rendered;
}
const RoleCardGrid = () => (
<Grid
maxColumns={[1, 2, 2]}
gridGap={[40, 40, 40]}
maxWidth={[null, 760, 760]}
rcLabel="rolePages"
>
{renderGridItems(gridItems)}
</Grid>
);
export default RoleCardGrid;
You can either introduce a flag property for each role, or you can expose a specialized RoleCard component for each role. The markup will look different. I will quickly sketch both solutions:
flag properties
// each role can only be shown once
// fixed order of roles
//usage
<RoleCardGrid designer manager doctor sales />
//metadata
const gridItems = {
designer: { roleTitle: "designer", linkTo: "example.com", reasoning:"Desc goes here" },
doctor: { roleTitle: "doctor", linkTo: "example.com", reasoning:"Desc goes here" },
// etc... add all roles here ...
};
//component
const RoleCardGrid = ({designer, manager, doctor, sales, freelancer, engineer}) => {
return (
<Grid
maxColumns={[1, 2, 2]}
gridGap={[40, 40, 40]}
maxWidth={[null, 760, 760]}
rcLabel="rolePages"
>
{designer && <RoleCard {...gridItems.designer} />}
{manager && <RoleCard {...gridItems.manager} />}
{doctor && <RoleCard {...gridItems.doctor} />}
{sales && <RoleCard {...gridItems.sales} />}
{freelancer && <RoleCard {...gridItems.freelancer} />}
{engineer && <RoleCard {...gridItems.engineer} />}
</Grid>
);
};
specialized RoleCard component
// each role can occur more than once
// order of roles can be varied
//usage
<RoleCardGrid>
<RoleCardGrid.Designer />
<RoleCardGrid.Manager />
<RoleCardGrid.Doctor />
<RoleCardGrid.Sales />
<RoleCardGrid>
//metadata
const gridItems = {
designer: { roleTitle: "designer", linkTo: "example.com", reasoning:"Desc goes here" },
doctor: { roleTitle: "doctor", linkTo: "example.com", reasoning:"Desc goes here" },
// etc... add all roles here ...
};
//component
const RoleCardGrid = ({ children }) => {
return (
<Grid
maxColumns={[1, 2, 2]}
gridGap={[40, 40, 40]}
maxWidth={[null, 760, 760]}
rcLabel="rolePages"
>
{children}
</Grid>
);
};
RoleCardGrid.Designer = () => (<RoleCard {...gridItems.designer} />);
RoleCardGrid.Manager = () => (<RoleCard {...gridItems.manager} />);
RoleCardGrid.Doctor= () => (<RoleCard {...gridItems.doctor} />);
RoleCardGrid.Sales = () => (<RoleCard {...gridItems.sales} />);
RoleCardGrid.Freelancer = () => (<RoleCard {...gridItems.freelancer} />);
RoleCardGrid.Engineer = () => (<RoleCard {...gridItems.engineer} />);
You could filter gridItems by checking if their roleTitle present in the props.
WARNING: This is just an example for inspiration:
let gridItems = [
{ roleTitle: "designer",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle: "manager",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle:"freelancer",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle:"engineer",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle:"doctor",
linkTo: "example.com",
description: "Desc goes here",
},
{ roleTitle:"sales",
linkTo: "example.com",
description: "Desc goes here",
}
];
// Component
import React, { useMemo } from 'react';
const RoleCardGrid = (props) => {
const selectedItems = useMemo(() => {
return gridItems.filter(({ roleTitle }) => roleTitle in props)
}, [])
return (
<div>
{selectedItems.map((item, index) => (
<div key={`gridItem-${index}`}>
<RoleCard
key={`roleGridItem-${index}`}
roleTitle={item.roleTitle}
linkTo={item.linkTo}
reasoning={item.reasoning}
/>
</div>
))}
</div>
)
}
// Then you can use
<RoleCardGrid designer manager doctor sales />
I have a datastructure of the form
node: { "name": "root";
"children": [ node ]; }
there is another example at the bottom of the question.
Now I would like to remove all nodes above a specified one and only keep the remaining sub-tree.
So for example, given the tree T
A
/ \
B C
/ \
D E
the function getTree(T, 'C') should return
C
/ \
D E
Question: is there an easy way to implement this?
function getTree(json, node) {
var tree = JSON.parse(json);
/* QUESTION: how do I remove everything not below the node with name===node here?
}
PS: larger example:
var tree = [
{
text: "Parent 1",
nodes: [
{
text: "Child 1",
nodes: [
{
text: "Grandchild 1"
},
{
text: "Grandchild 2"
}
]
},
{
text: "Child 2"
}
]
},
{
text: "Parent 2"
},
{
text: "Parent 3"
},
{
text: "Parent 4"
},
{
text: "Parent 5"
}
];
Edit: good point: I should have mentioned that the node names are unique.
You could iterate the array and look if the node has the wanted text or if the nested nodes have a found.
const
getTree = (tree, text) => {
let result;
tree.some(node => result = node.text === text
? node
: getTree(node.nodes || [], text)
);
return result;
},
tree = [{ text: "Parent 1", nodes: [{ text: "Child 1", nodes: [{ text: "Grandchild 1" }, { text: "Grandchild 2" }] }, { text: "Child 2" }] }, { text: "Parent 2" }, { text: "Parent 3" }, { text: "Parent 4" }, { text: "Parent 5" }];
console.log(getTree(tree, "Grandchild 1"));
console.log(getTree(tree, "Parent 1"));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I'm trying to map through objects to display their values in my React JS project. It looks like I can't access values of the objects within objects using the map function, values simply are not displayed, or if I try to use (.split("/p")) it gives me an error saying "split is not a function".
Link to codesandbox
this is my code:
import React from "react";
import { studies } from "./studies";
import "./styles.css";
const Data = ({ title, text, number, numberdesc }) => {
return (
<>
<div style={{ margin: "1rem" }}>{title}</div>
<div style={{ margin: "1rem" }}>{text}</div>
<div>{number}</div>
<div>{numberdesc}</div>
</>
);
};
function App() {
const appComponent = studies.map((post, i) => {
console.log(studies);
return (
<Data
key={i}
number={studies[i].post.number}
title={studies[i].post.title}
text={studies[i].post.text
.split("/p")
.reduce((total, line) => [total, <br />, <br />, line])}
/>
);
});
return <>{appComponent}</>;
}
export default App;
and {studies} file:
export const studies = [
{
id: "first",
text: "1st Post",
post: {
data: [
{ number: "100", numberdesc: "description1" },
{ number: "200", numberdesc: "description2" },
{ number: "300", numberdesc: "description3" }
],
text: [
{
title: "Title1 from the 1st post",
text: "Text1 from the 1st post."
},
{
title: "Title2 from the 1st post",
text: "Text2 from the 1st post."
},
{
title: "Title3 from the 1st post",
text: "Text3 from the 1st post"
}
]
}
},
{
id: "second",
text: "2nd Post",
post: {
data: [
{ number: "100", numberdesc: "description1" },
{ number: "200", numberdesc: "description2" },
{ number: "300", numberdesc: "description3" }
],
text: [
{
title: "Title1 from the 2nd post",
text: "Text1 from the 2nd post "
},
{
title: "Title2 from the 2nd post",
text: "Text2 /p from the 2nd post"
},
{
title: "Title3 from the 2nd post",
text: "Text3 from the 2nd post"
}
]
}
}
];
What I want to do is to access data and text values for each post, and display them in my Project. Any help and suggestion is greatly appreciated,
Thank you.
I think you may be wanting to .map your array of content. For example:
text={studies[i].post.text.map(t => <p><strong>{t.title}</strong>: {t.text}</p>)}
might replace the existing line that is breaking.
Is this what you're looking for?
function App() {
const appComponent = studies.map(study =>
study.post.data.map((data, k) => (
<Data
key={k}
number={data.number}
numberdesc={data.numberdesc}
title={study.post.text[k].title}
text={study.post.text[k].text}
/>
))
);
return <>{appComponent}</>;
}
Note I arbitrarily zipped data[k]'s number and numberdesc with text[k]'s title and text, but that might not necessarily be what you intend to display.
The above will likely break in case your data and text arrays do not have the same length in any given study.
See it here.