As far as I've read parents should store childrens data which would solve my problem simply because I could iterate through the tree, but I'm not able to come up with a solution that manages to do that in react.
To clarify:
My goal is to convert my JSX-Layout to a JSON-Object so that I can send it to my backend for further processing.
Currently each of the nodes handle the adding of a new child in themself. All nodes only know their immediate children.
The problem now is that I don't know how I could read the entire tree data structure.
For example: In Java I could simply add a child to a node by just accessing the node's reference and adding the new node to the list of childrens. Since I use functional components in React I'm not able to do that and probably need to pass down a event from the parent nodes to register new nodes from children but I simply cant wrap my head around it. I don't see how that is possible with React, but I'm certain that it's just me misunderstanding something or trying for too long.
Any help is appreciated!
TL;DR:
How do I manage state in trees so that I can access the whole tree and add/remove children to/from children and their descendants?
I tried to pass down an event from the parent through the whole hierarchy where every child would add its children but I'm not sure if that is correct since it seems really unclean.
If I'm understanding your question correctly, you want a way to traverse your tree. There are various ways to traverse a tree to generate a full depiction of its elements (to make a JSON object, for example), but I think you probably want to use in-order traversal. Here is a link with more details about some different tree traversal strategies.
If you're looking for a way to access a React component's children, you can read this article for inspiration.
What I need to get done
I want to make a simple controlled lexical plaintex editor - one which is controlled by a parent string field.
But I'm really struggling with getting my editor to simultaneously:
Be adopting parent state whenever it changes
Retain Selection after adopting parent state
Not automatically focus just because the external value changed (and got adopted)
Where I got so far
I tried a couple things, but this is the closest I got - Sandbox here:
export const useAdoptPlaintextValue = (value: string) => {
const [editor] = useLexicalComposerContext();
useEffect(() => {
editor.update(() => {
const initialSelection = $getSelection()?.clone() ?? null;
$getRoot().clear();
$getRoot().select(); // for some reason this is not even necessary
$getSelection()?.insertText(value);
$setSelection(initialSelection);
});
}, [value, editor]);
};
This approach works well when writing into the input itself, but the imperative adoption of value only works until the input was first selected. After it has already been selected (even when un-selected again), editor only adopts the value for one "render frame" and then immediately re-renders with the old value.
I'm clearly doing something wrong with selection, because:
removing setSelection(initialSelection) also removes this problem - but then selection doesn't get maintained between updates, which is also unacceptable.
I'm getting this error on every keystroke:
updateEditor: selection has been lost because the previously selected nodes have been removed and selection wasn't moved to another node. Ensure selection changes after removing/replacing a selected node.
... It seems to me that initialSelection retains a reference to nodes that are deleted by $getRoot().clear(), but I tried working my way around it and got nowhere.
I'll be glad for any advice/help about my code or towards my goal.
Thank you 🙏
Bit of background information on how Lexical works (feel free to skip to the next point)
Lexical utilizes EditorState as the source of truth for editor content changes and selection. When you do an editor.update, Lexical creates a brand new EditorState (a clone of the previous) and modifies it accordingly.
At a later point in time (synchronously or asynchronously), these changes are reflected to the DOM (unless they come from the DOM directly; then we update the EditorState immediately).
Lexical automatically recomputes selection when the DOM changes or nodes are manipulated. That's for a very good reason, selection is hard is to get right:
Selected node is removed but has siblings -> move to sibling
Selected node is removed but has no siblings -> find nearest parent
Text node content changes -> understand whether the current selection fits
DOM selection changes because of composition or beforeinput -> replicate selection
etc.
This selection recomputation is also initially done to the EditorState (unless it comes from the DOM directly) and later backed to the DOM.
Focus restoration
By default selection reconciliation will restore DOM selection to make sure it matches the source of truth: the EditorState. So wherever you move the selection (even if it's part of the automatic selection restore described above) will move the focus to the contenteditable.
There are 3 exceptions to this rule:
$setSelection(null); -> clears selection
readonly editor.setReadOnly -> you are not supposed to interact with a readonly editor
Collaboration editor.update(() => ..., {tag: 'collaboration'}) -> we created an exception for this
I would never recommend 1. for this purpose since the editor will lose track of the position.
The second makes sense when the editor is truly readonly.
The third can work for you as a temporary patch but ultimately you want a better solution than this.
Another temporary patch for your use case would be to store document.selection and restore it as soon as the Lexical contenteditable takes control.
That said, it seems like a reasonable use case to be able to skip DOM selection reconciliation at times programatically. I have created this proposal (https://github.com/facebook/lexical/pull/2134).
Side note on your error
Your selection is likely on the paragraph or some text node. When you clear the root, you destroy the element. In most cases, we attempt to restore the selection as listed above but selection restoration is limited to valid selections. In your case you are moving the selection to an unattached node (already removed as part of root.clear()).
How can I either reload a whole tree and/or a single node allong with all childs of these node from the scope of the node.
The Single-Node currently this is done by expanding but now there is the need for a reload button for the whole grid and for the selected node. I looked at the nodeinterface there is is nothing useful like a reload. The node are using a automodel at the moment, meaning no explicit model is created.
revished edit
I also tried to call removeAll() on a treestore and also removeAll() on the tree. The first calls the reader method for each removed node with the node itself as param which then result in a error cause there server answer all the invalid request with a empty result. The second removes all but also with errors.
Any help is appreciated!
For your second question:
You might try call the removeAll() of the tree instead of the store.
hi i have a treePanel on node dblClick listener i am opening a tab panel...it works for the most part but on some occasion i am getting this error el.cache[] null or not an object...does any one have any idea on how to solve this problem..please help
Sounds like an event isn't being cleaned up. Make sure that if you are listening to an event for a node in the tree, that the event will be cleaned up (that is, "unlistened") properly if the tree gets reloaded.
Are you ever reloading nodes in the tree? If so, I'd check any events tied directly to nodes.
If not, try and let us know if you are doing anything else funny with the tree, and see if you can post some code.
I'm writing a silverlight app that queries a web service to populate a tree control. Each element will have at least 2 levels of children, so something like this:
a
+-b
+-c
d
+-g
+-h
e
+-i
+-j
f
+-k
+-l
The web service API is such that I can only get one level of child nodes at a time, so the first trip, I can get a,d,e,f. To get b,g,i,k, I have to make 4 trips. Similarly, I have to make 4 more trips to get c,h,j,l. (The service does actually allow me to get all the nodes in one trip, but it doesn't give me parent-child relationships along with it :-()
My question is this: should I make the user wait for a while up front while I get all the nodes for the tree view, or should I just get the top few nodes, and get the other nodes on-demand, or in a background task? Also, the nodes can change asynchronously, so if I get all the nodes up front, I'll need a "refresh" button for the treeview, and if I do it on demand, I'll have to have a caching strategy.
Which is best for the user?
A compromise where you load the first level up front and then load the remaining items in the background overridden by on-demand as required. If you load the nodes breadth first (e.g. a,d,e,f then b,g,i,k) rather than depth first (e.g. a,d,e,f followed by b,c) you can redirect your loading to be focused on the most recently expanded node.
Personally, as a user, I would prefer all the data to be loaded up front so that once the application finishes loading I can trust that I won't have to wait anymore (or at least very little)
But, I suppose it depends on several traits of your application / data:
How dynamic is the data? Does it update more often then the rate at which the user explores the nodes? If it does, then you will have to read the data as the user explores it, otherwise you can probably get away with only updating it occasionally and checking for the freshest data before performing important operations.
How much of the data will the user explore during normal use? If they are constantly exploring throughout the entire tree, then having the entire tree loaded is important. On the other hand, if most users will usually only expand a small portion of the tree, then maybe loading on demand is better so you don't waste thier time loading data they will never see anyway.
How much affect with this have on performance? Does it really take a long time to load all the data? If the data is not too much, maybe the whole thing can be loaded in a matter of seconds, in which case the amount of work to implement the optimization will not be significant to the end user and in turn will not have a good return on investment.
Most likely you don't have clear cut answers to these questions, but they're probably good to consider when you're attacking this interesting problem.
Short answer is to make the user wait for as little as possible. They will curse your name if they have to wait 10-20 seconds on application load, but not notice 0.1-0.2 seconds for a tree node to expand.
I have an app in production with a similar structure. I cannot load up-front because it'd be effectively loading the entire database. Here's my strategy:
The tree control starts with 1 level expanded below the root.
Each unexpanded node has a dummy child node in order to get the [+] expansion icon to show
When a node is expanded, it fires an event which is trapped by the app. If the only child node is the dummy one, the dummy is deleted and the children are loaded from the database.
Changes in the data are not reflected automatically by visible nodes, however the context menu for the tree has a Refresh item that can be used to refresh a node.
I have considered showing updates asynchronously, but have tended to avoid it because large amounts of data can be shown in the tree and I'm wary of DB load if I'm checking them all for changes.
The app is WinForms, written in C# using .NET 2.0.