I want to create a tree that allows the user to be able to add/remove nodes by clicking on the node that the user would like to add children to/remove the node they click on. I am using the react-expendable-treeview package as I like how it looks visually. Javascript functionality can be found under src/lib/components/
The github repo can be found here: https://github.com/fosco/react-expandable-treeview and the tree looks like so: https://imgur.com/7Y5xcIj
The data that is passed into the TreeView component is predefined in a javascript file like so:
const testData = [
{
id: 0,
name: "Felidae",
children: [
{
id: 1,
name: "Pantherinae",
children: [
{
id: 2,
name: "Neofelis",
},
{
id: 3,
name: "Panthera",
}
]
},
{
I'm wondering how I can set this up so I am able to add/remove children. I am aware that there are many other react tree packages such as react-d3-tree. However, I visually like how this one looks. If anyone knows how I can modify this package or change the theme of react-d3-tree or another package to look like this, please let me know.
Related
My problem - Get components & props from MDX/JSX string
I have an MDX string with Front-matter meta data in YAML, some regular text, some markdown and some React components.
I need to get the list of all React (non-HTML) components from it with their parameters.
So given this example:
---
title: Title of documents
tags: one, two, three
---
# My heading H1
Some text in paragraph. Than the list:
- First item
- Second item
More text with [a link](https://example.com).
<Articles category="theatre" count={3} />
Further text with more information.
<Newsletter categories={['theatre', 'design']} />
<MultilineComponent
paramA="A"
paramB="B"
/>
<ComponentWithChildren param="value">
Some children
</ComponentWithChildren>
... I would need this output:
[
{
component: 'Articles',
props: {
category: 'theatre',
count: 3,
},
},
{
component: 'Newsletter',
props: {
categories: ['theatre', 'design'],
}
},
{
component: 'MultilineComponent',
props: {
paramA: 'A',
paramB: 'B',
}
},
{
component: 'ComponentWithChildren',
props: {
param: 'value',
}
}
]
Also I need to do this on the server, so I don't have access to browser functionality (window, document, etc.).
What I've tried
Some basic Regex, but as I'm far from being professional in regexing, now I have two problems. :)
Is there some built in way how to parse JSX string to get a list of components & props in the way that I've described above? Or is there some maybe some other parser that I can use to solve this? If not, is there some Regex pattern I can use to get this?
Quick summary on "Why"
During the build of my Next.js project I need to determine which data is actually needed for each MDX page in the bundle. So if I see this in the Mdx file:
...other text
<Articles category="theatre" count={3} />
...other text
... which I'm somehow able to parse to this:
component: "Articles"
category: "theatre"
count: 3
that's enough info for me to know that I need to send those data to the page:
[
{
title: 'Romeo and Juliet',
category: 'theatre',
},
{
title: 'The Cherry Orchard',
category: 'theatre',
},
{
title: 'Death of a Salesman',
category: 'theatre',
}
]
Would you be able to help me with this? Thank you in advance! 💪
Edit
#Rango's answer pointed me to the right direction! One caveat: jsx-parser can not handle multiline components to which Rango's proposed the following solution:
if (rsp.test(c)) continue; // add before /index.js:374
This however removes all whitespace from string attributes. So I've replaced it with this:
if (/[\n\r]/.test(c)) continue; // this should remove lines only
So far this solution works. I would be more comfortable to use more stable libraries, but unfortunately none of the proposed solution worked for me (acorn-jsx, react-jsx-parser, babel/parser).
Not sure that parsing JSX with regular expressions is efficient because curly brackets {...} can contain any JS expression, so if you choose this way then prepare to parse Javascript as well.
Fortunately, there are a bunch of JSX parsers that can do it for you. E.g. the first one I picked was jsx-parser and this small beast can parse your example (with a simple trick). The shape of the result is quite different but you can transform it to match your needs.
var test = `
---
title: Title of documents
tags: one, two, three
---
# My heading H1
Some text in paragraph. Than the list:
- First item
- Second item
More text with [a link](https://example.com).
<Articles category="theatre" count={3} />
Further text with more information.
<Newsletter categories={['theatre', 'design']} />
<MultilineComponent
paramA="A"
paramB="B"
/>
<ComponentWithChildren param="value">
Some children
</ComponentWithChildren>
`
const components = [...test.matchAll(/<[A-Z]/g)]
.map(match => JSXParser(test.slice(match.index)))
document.getElementById('result').textContent = JSON.stringify(components, null, 2)
<script src="https://unpkg.com/jsx-parser#1.0.8/index.umd.js"></script>
<pre id="result">Hello</pre>
In my snippet I used UMD version of the package, but for node.js consider choosing ES module ofc.
Imagine the following list:
Managing Director
Sales Director
IT Director
Technical Lead
Software Developer
Support Technician
HR Department
HR Officer
HR Assistant 1
HR Assistant 2
It's backed by a state in the form of:
[
{
id: 1,
text: "Managing Director",
children: [
{
id: 2,
text: "Sales Director"
}
...
]
}
...
]
Now I want to indent Support Technician. I would modify the state array to remove the item from the Technical Lead parent & add it to the Software Developer parent. The problem is, that React first deletes it, which causes all items below it to jump one line up, and then in the next frame adds it again to the new parent, which pushes those items a line down again. This appears as a flicker. It doesn't happen every time (sometimes react manages to render both in the same frame), but often enough it happens and is very distracting.
The state is modified in a way, that the parent passes its state callback setState down to its children. In this case, the initial state of the Technical Lead node looks like:
{
id: 4,
text: "Technical Lead",
children: [
{
id: 5,
text: "Software Developer"
},
{
id: 6,
text: "Support Technician"
}
]
}
As obvious from the state, every node renders all its children recursively.
After the indention, the state is modified to the following:
{
id: 4,
text: "Technical Lead",
children: [
{
id: 5,
text: "Software Developer",
chiilderen: [
{
id: 6,
text: "Support Technician"
}
]
}
]
}
If I were to this without React and instead with regular DOM APIs, I would move the node to the new parent with something like insertBefore(). React on the other hand unmounts & remounts the node.
Below is a simplified example of my "Node" component, which renders the list:
const Node = ({data, setSiblings}) => {
const [children, setChildren] = useState(data.children)
function indent() {
setSiblings(siblings => {
// const prevSibling = find the item in the state array
// const thisNode = {id, text, children}
const newPrevSibling = {...prevSibling, children: [thisNode]}
const siblingsWithout_ThisNode = deleteFromArray(siblings, thisNodeIndex)
// updateAtIndex() returns a new array with the modification (immutable)
return updateAtIndex(siblingsWithout_ThisNode, prevSiblingIndex, newPrevSibling)
})
}
const childNodes = children?.map(child =>
<Node data={child} setSiblings={setChildren} key={child.id}/>
)
return (
<li>
<div>{data.text}</div>
{childNodes ? <ul>{childNodes}</ul> : null}
</li>
)
}
The indent() function is triggered by a Tab press, but I didn't include the key handler logic here
I didn't find a solution to this problem directly, but I switched to using MST (MobX-State-Tree) for state management now and it worked with it (didn't flicker anymore - seemingly, both the unmounting & remounting of the component happen in the same frame now).
Below is a CodeSandbox with the MST implementation. When clicking e.g. on the Support Technician node, you can press Tab to indent and Shift + Tab to outdent (you have to click again, since it loses focus)
https://codesandbox.io/s/priceless-keldysh-17e9h?file=/src/App.js
While this doesn't answer my question directly, it helped solve the problem semantically (it's better than nothing).
I need to show a tree from a flat array of data using Angular and I'm open to use any package to render the tree. as a user, I should be able to click on a node and find out details such as node ID and title. the tree should be expanded on load but users should be able to collapse parent nodes as they wish. my node data model looks like below:
export class Node {
nodeID: number;
title: string;
parentNodeID: number;
}
and my data looks like this:
public Nodes: Node[] = [
{
nodeID: 1;
title: parent1;
parentNodeID: null;
},
{
nodeID: 2;
title: child1;
parentNodeID: 1;
},
{
nodeID: 3;
title: child2;
parentNodeID: 1;
}
]
you need a recursive algorithm that looks loops through your flat array and maps parentNodeID to Node to generate tree structure and then use a tree component, for example angular-tree-component, to render your tree.
I made a demo on stackblitz. have a look and let me know if it helped.
https://stackblitz.com/github/ramin-ahmadi/Flat-Tree
There is plenty of package that could do the job, for example This one. I did not tried it but seems easy to use. If you can, change your keys in your array. Else, then just map your items into another array, something like:
const newArray = array.map(item =>({
id: item.nodeID,
name: item.title,
children: array.filter(el => el.parentNodeID === parentId), // Not sure about that, but this is the idea
})
);
newArray wil be the data provided to your three.
I have a data structure typed like:
export interface IGroup {
id: number;
name: string;
groupTypeId: number;
items: IItem[];
groups: IGroup[];
}
Which recursively represents many to many relationships between a "Group" and a "Group" and an "Group" and an "Item". Groups are made up of items and child groups. An item derives to just a simple type and other meta data, but can have no children. A single group represents the top of the hierarchy.
I currently have components, hooks, etc to recursively take a single group and create an edit/create form as shown below:
I have this form "working" with test data to produce a standard data output as below on save:
{
"1-1": {
"name": "ParentGroup",
"groupType": 2
},
"2-4": {
"name": "ChildGroup1",
"groupType": 1
},
"2-9": {
"name": "ChildGroup2",
"groupType": 3
},
"2-1": {
"itemType": "FreeForm",
"selectedName": "Testing",
"selectedClass": 5
},
"2-2": {
"itemType": "FreeForm",
"selectedName": "DisplayTest",
"selectedClass": 5
},
"3-4": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900503,
"name": "TRUE"
}
},
"3-5": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900502,
"name": "FALSE"
}
},
"3-9": {
"itemType": "FreeForm",
"selectedName": "Test",
"selectedClass": 5
},
"3-10": {
"itemType": "FreeForm",
"selectedName": "Tester",
"selectedClass": 5
},
"3-11": {
"itemType": "FreeForm",
"selectedName": "TestTest",
"selectedClass": 5
}
}
The "key" to these objects are the grid column and row since there are no other guaranteed unique identifiers (if the user is editing, then it is expected groups have ids in the db, but not if the user is adding new groups in the form. Otherwise, the name is an input form that can be changed.) It makes sense and it is easy to model the keys this way. If another group or item is added to the hierarchy, it can be added with its column and row.
The problem that I have is that I would love to be able to have an add button that would add to a groups items or group arrays so that new rows in the hierarchy could be created. My forms should handle these new entries.
Ex.
"1-1": {
groups: [..., {}],
items: [..., {}]
}
But the only data structure that I have is the IGroup that is deeply nested. This is not good for using as state and to add to this deeply nested state.
The other problem I have is that I need to be able to map the items and groups to their position so that I can translate to the respective db many to many tables and insert new groups/items.
Proposed solution:
I was thinking that instead of taking a group into my recursive components, I could instead create normalized objects to use to store state. I would have one object keyed by column-row which would hold all the groups. Another keyed by column-row to hold all the items. Then I think I would need two more objects to hold many to many relationships like Group to Group and Group to Item.
After I get the data from the form, I hopefully can loop through these state objects, find the hierarchy that way and post the necessary data to the db.
I see that this is a lot of data structures to hold this data and I wasn't sure if this was the best way to accomplish this given my modeling structure. I have just started using Redux Toolkit as well, so I am somewhat familiar with reducers, but not enough to see how I could apply them here to help me. I have been really trying to figure this out, any help or guidance to make this easier would be much appreciated.
Go with normalizing. Each entity having a single source of truth makes it much easier to read and write state.
To do this, try normalized-reducer. It's a simple higher-order-reducer with a low learning curve.
Here is a working CodeSandbox example of it implementing a group/item composite tree very similar to your problem.
Basically, you would define the schema of your tree:
const schema = {
group: {
parentGroupId: { type: 'group', cardinality: 'one', reciprocal: 'childGroupIds' },
childGroupIds: { type: 'group', cardinality: 'many', reciprocal: 'parentGroupId' },
itemIds: { type: 'item', cardinality: 'many', reciprocal: 'groupId' }
},
item: {
groupId: { type: 'group', cardinality: 'one', reciprocal: 'itemIds' }
}
};
Then pass it into the library's top-level function:
import normalizedSlice from 'normalized-reducer';
export const {
emptyState,
actionCreators,
reducer,
selectors,
actionTypes,
} = normalizedSlice(schema);
Then wire up the reducer into your app (works with both React useReducer and the Redux store reducers), and use the selectors and actionCreators to read and write state.
Situation
I currently write a news reader app for a magazine, which publishes the content in English and German under different categories. The number of categories per language is different. The categories are stored in as an array per language.
CATEGORIES_EN = [
{
selector: '*',
blog: BLOG_EN,
id: `${BLOG_EN}_*`,
},
{
selector: 'Politics',
blog: BLOG_EN,
id: `${BLOG_EN}_Politics`,
},
// ... 8 more
];
CATEGORIES_DE = [
{
selector: '*',
blog: BLOG_DE,
id: `${BLOG_DE}_*`,
},
{
selector: 'Politik',
blog: BLOG_DE,
id: `${BLOG_DE}_Politik`,
},
// ... 9 more
];
The screen component is always the same, but must receive the selector and the blog somehow.
Question
How can I change the number of screens, when the the language has changed?
How can I assign the categories to the screen component?
Environment
react-navigation: 1.0.0-beta.11
react-native: 0.45.0
Git-Issue
https://github.com/react-community/react-navigation/issues/1872
I've found a solution and want to share it with you:
You have to recreate the TabNavigator all the time when something has been changed.
For details please look in my Git-Issue and the referenced issue.