How to imperatively set value of #lexical/react plaintext editor, while retaining `Selection`? - reactjs

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()).

Related

Clean way to manage an "edit view" for a single item in React/Redux/React-Router

I've often come across a situation where I have a list of items in my store (loaded at "boot") and I'm implementing an "edit item view". There are numerous ways to do this but I've yet to find one that really feels solid given a few "constraints".
My edit view "constraints":
The selected item must be copied to its own reducer (activeItem). This is
to a) give me a comparison point so I can tell the user if the item
has changed or not) and b) in some cases, it allows me to update the
main item list without affecting the edited item
I want the URL to reflect that I'm editing an item and obviously
directly navigating to the item should work (I typically use React-Router)
If I have an "editItem" action, 1 is easy but fulfilling 2 feels hacky. I need to parse URL params, await that the main list is loaded and then call editItem. A bit of a mess.
If I accept that the URL should be the source of truth for active item, 2 is easy but I'll have to manage a bit more state in my edit component to do 1 (check if an active item is, and if not, dispatch an editItem action.
Is there a good, clean pattern I'm missing?
(Third approach would be to use URL as the source of truth for active item and never "copy" the item over. This means I don't have a comparison point for "has user changed this" though and doesn't allow me to update the "main list" in the background).
To me it makes sense to only use the redux store for data which is (or at some point in the future may need to be) accessed from different parts of the app. This helps keeps the structure of the store clean and easy to understand.
Temporary activeItem fields etc on the store which will only be looked at by a single display component over a single component lifecycle just add bloat, when this data could be nicely tucked away on the state of the only interested component.
Therefore I vote to keep the URL as the source of truth for the active item, and for letting the edit component keep a copy of the original version and perform any "has been changed" checks itself. I would say this is preferable regardless of the added plus of simple direct linking to your URL.

Idiomatic way to chain redux state changes?

I'm trying to figure out the idiomatic way to address this:
I have a reducer that contains a list of “applied filters” and a list of “records”. When I change the filters or records, a list of “filtered records” should also change, but applying the filters to what can be a long list of records can be slow-ish.
Right now, I’m dispatching an “Add filter” or “Remove filter” action to change that list of “applied filters”. This results in recalculating the list of “filtered records” (currently done in the reducer).
I'm using two container components, one to display a list of "filter chips", the other to display the list of "filtered records".
Because applying the filters takes a while, and is done every time the filters change, the process of removing a filter from the UI seems laggy -- I click to remove a filter, and the removed filter doesn't disappear until the reducer finishes all its work, which includes updating the list of filters AND applying the new list of filters.
What I'd rather have is:
Remove the filter, and it disappears from the UI as quickly as possible. (i.e., a state change is broadcast that just includes the removed filter)
Concurrently, I'd like the process of applying the filters to occur in the background, and once that is finished, dispatch another action to change the list of filtered records.
After the filters have been applied and the action to update the filtered records has fired, I want my "filtered records list" component to update.
So, essentially I want a state change to potentially trigger ANOTHER state change, but I want to broadcast the intermediate state in between those two state changes.
I know I have to get the bulky logic out of my reducers and into an action, but I'm struggling with how/where that logic should be applied. I have that nagging feeling that I've wrapped my brain around its axle and I'm now overcomplicating things, but I can't seem to get back to the simple/correct method.
Any help would be greatly appreciated!
Edit, from a comment I added, but wanted to keep it inline here as well:
I mis-spoke in my original question, pretty big correction here --
I'm not actually calculating the "filtered record list" in the reducer -- I have a reselect selector on my "RecordList" container that takes in "records" and "filters" and returns a list of the filtered records.
I'm thinking this is more because of the "RecordList" render holding up the "FilterList" render, so I'm going to go up the component hierarchy and see if I can fix something there.
I'd still be up for any suggestions, though!

React redux: listing and editing. Should use one array of items or array of items and separate edittingItem?

If in my React app, utilizing Redux and redux-thunk, my store has an array of relatively lightweight/denormalized items that is used in a listing of said items (paginated in the API), but then I also have an edit/add option for each (which is the same item as in the listing array but with many additional fields, not de-normalized, etc.), I don't know which of the following is the best approach for storing in my store:
Maintain one array of items in my store, which contains all necessary data (normalized, and therefore each contains a relatively deep object graph). Editing simply requires finding it in the store, making the changes, and slicing back into the state.items array.
Maintain an array of minimalist items for the item listing, while also having a root state.currentItem object.
Maintain an array of minimalist items for the item listing, while resetting the array to contain only one item (containing all necessary data for editing). This avoids needing to create another reducer which may or may not be a good thing.
Because of the data-intensiveness of the actual listing item object, I have to go with option 2/3, but now I've got redundant data in two places that conceivably won't be in sync (although practically speaking, in my app, because of paging/sorting I must necessarily re-poll for latest data from the API upon returning back to the listing) or an array of items that can contain items in two different object formats.
Both feel dirty, which leads me to believe I'm missing something obvious that redux is already equipped to handle. Suggestions? Additional options?
Can the user update multiple items at the same time? Redux has 3 important principles to respect. One of them is the single source of truth, which means all valid/true data must come from one place (store here) and every data element is stored exactly once. Having multiple time the same items in the store is bad, mainly because of sync problems like you said. Also having deep objects is not a good approach, you should keep your state flat. Since you're using react with redux, you have access to state, the app state with redux and the component state with react. The most common pattern with this kind of problem is to create a copy of the object that you are editing in the component state. If the user cancels the editing, it will be easy to revert it. If the user stops editing the item without saving, you app state won't be affected. Also, you will avoid polluting your app state with wrong or duplicated data. Let's say that in your list when a user clicks on an item, the item transform to a text input. The list item will be base on the redux store item and the text input will be base on a copy of the same item. This also applies to new objects. You can also validate your item before adding/updating it. Keep in mind that the most important part is to keep your store clean because you will base you displayed items on that, so fetching new items won't affect your list. Doing this will also help you updating the item in the store because you will only have to replace it when dispatching a save action.

Inserting text into input in descendant component with React

I'm diving into react for the first time but I'm having some difficulty with a particular requirement. I have the following component structure:
Survey
--QuickTextOptions
----QuickTextOption
--Question
----Answer
------TextAnswer
As you probably can tell, I'm working on a survey application. A survey author would create a question, then answers (i.e. sub questions or options). There will be a number of different answer types including checkbox and text.
Also at the top of the survey, above the questions, are a list of "quick text" options. When one of these is selected, some text is appended to the value field of the appropriate TextAnswer's input field. A user can select as many of these as they'd like.
This is trivial to do with just javascript, but I can't figure out a good way to do this with React without adding refs to components down the chain of the survey.
If I pass it in as a prop, have AnswerText as a controlled component, and set value={this.state.value + this.props.quickText}. It just re-inserts the text on the handleChange event of AnswerText.
I'm considering using refs and calling child functions from parents but refs seem to be discouraged and I'll have to do this for Question, Answer, and AnswerText in order to pass it down the chain which seems a bit too much.
Is there any other way to fire an event to a descendant down the chain with some data? It's a one time addition so props seem to not work well for this.
Do you use something like FLUX and global state? In my practice, usually you build such app working around global state which stores your input data. This way you can edit it transparently from different places according to action events (the only ones which can modify that input value, inputs themselves are not doing that).
So I see that it should look like this:
You pass references to global state object deep into your components and to your form's input.
When you start typing each new character triggers event to modify global state value for this input. You're getting updated value instantly in your component's props and component renders newly entered character.
When you want to modify input from some other component (by pressing button) you do almost the same. You click on button and it triggers event saying that global state value for that input should have "some string" added. Your input component receives callback with new props and renders this text instantly.
By saying event I mean special event which gets some value and inserts it into global state. Rest is done by framework.
Hope this will help you to design your app. The approach I described above is a short description of how Tictail Talk web chat client works now.

Calculating data dependent on non-committed (state) data of child/distant components in React

TL;DR edit in retrospect years later: there's no solution that's not gross as long as it's just state data - you'll need to also get it into a separate store somewhere somehow and can do whatever you want at that point. But read the question and the answer and the back-and-forth if you want some more background.
I have a table of two sections, each with various input values. Let's say that it is a survey. Feeding data into this is straightforward; I have the typical model:
{ "sections": [ { "name": "a", values: { "A": 1, "B": 2, "C": 1, ... } }, ... ], ... }
And a component hierarchy like:
<Survey>
<Section> (for each section)
<ValueRow> (for each value)
I put the model into a prop on the survey and the right information is trickled down into the subcomponents. Each ValueRow has a text field and its ephemeral value reflected back into its own state. This works fine "on the way down", in the one way flow that React is built for.
However, I also wish to show progress on the Section level and for the entire Survey, both simple things like number of fields filled out and statistical data needing the entire data set - what's the average across sections, how many "1" answers do I have in total, what's my grade (calculated from all the answers) and so on. Essentially, I'd also want to have:
<Survey>
<SurveyWideStats>
<Section> (for each section)
<SectionWideStats>
<ValueRow> (for each value)
This turns into a reduction of the current state instead of the model data. What's the best way of doing this in React? Flux and Actions and Stores all seem to deal with how to handle the data once it has been committed to the model. What I want to do is to pluck all the state data and do something with it, but it also seems terribly gross for the SurveyWideStats element, for example, to go poking through the garbagestate of its sibling element's children.
My current solution is to pass around an accumulation object and provide enough state to each component that it can keep calling that whenever something changes. This seems clear and divided enough, but it means that I have to have two passes and have to be careful not to start fiddling with state during rendering (at least since that's when I call the accumulation object - I suppose there may be a better point during the lifecycle where I could call that). And in addition, it seems like this would be an obstacle to "pick up from" server side rendering.
What's the best way? Is there an established pattern for this - preferably one where these things don't have to be so custom and really tailored to the data all the time?
Two ways to do this:
Pass the entire table as a prop to the highest component .
Inside survey's render function, calculate the stats, then pass them to the component as props, followed by the foreach loops over the table for the other children components. That way, your stats component is a pure component, does not need state and does not need to poke in siblings.
Create a stats function in a store, and have the component call this to get the stats. NB best not to save the stats in a store, since it is clearly derived data. Unless for performance reasons.
Hope this helps!
UPDATE:
To handle changes by the user when they change an input value, you have two options, depending on your preference:
(Option 1 describes a pure component).
(When you use flux pattern): Put the value of the input control in props. And whenever the user makes a change, fire an action to update a store, and have the store pass down updated props. So the (top) component notices a change event and rerenders. This creates more or less 'live' updates, e.g. when a user types a single character in an input field, the page title is updated immediately. The component with the input control does not have (and does not need) setState. This setup may become slow in really large component trees (because with each character, the entire tree is rerendered). But react is superfast and smartly only renders changes in de tree.
Put the initial prop value in state (in getInitialState() and put the input value in state also. Typical example: user types a character in an input field, the change triggers a setState() and the component is rendered again. Only when the user clicks some save or commit button, an action is fired to save the value in a store.
UPDATE:
As a bonus, below the flow for updating stores and components.

Resources