I have a function updateRowValue, which updates a value in a row in a table based on user input. There are 11 columns whose values can be updated by the user, and the user can add/remove rows. I am currently testing on a table with 7 rows, and there is a severe lag between pressing a key on the keyboard and the change appearing on the screen.
Each time the user presses a key on their keyboard, the state is updated (I am using a controlled MUI TextField). I don't think the amount of inputs is the actual problem (though I might be wrong). I think the problem is that I have to traverse through the table_rows object every time a value should be updated.
My question is basically: Can I update the value each time the user presses a key on their keyboard and also make the app feel responsive to the key presses? If so, then how?
function updateRowValue(row_id, attribute, new_value){
const new_row = Object.assign(table_rows[row_id], {[attribute]: new_value});
const newState = Object.assign(table_rows, { [row_id]: {...table_rows[row_id], ...new_row} });
setTableRows({...table_rows, ...newState});
}
I found an alternative to the whole mess I had created. Instead of trying to optimize how I save the states (which might not even be possible) I realized that I don't actually need the states. I can access the form values directly.
By adding inputProps={{"data-row": row_id, "data-cell": cell_id}} to the TextField, and then using document.querySelectorAll("[data-row]"), it is easy and fast to access all cells and their data when it is actually relevant to use them (saving them to the database in my case).
Related
Within my page (index.tsx), I render a component which can have many children (grandparent.tsx). When I hit delete with no option selected for the input, it deletes fine from my global state (testAtom in atom.ts). However, if I select an input option, when hitting delete, it resets the input option i.e. it doesn't delete in the intended way. The intended behaviour is for it to remove the relevant index from the testAtom.
I believe it is something to do with the useEffect within the input.tsx component. I currently have this here because if the user changes the condition dropdown, I want the value to update accordingly. However, I've been stuck on this for a while and cannot figure it out, so any pointers would be helpful. I would recommend a look at the codesandbox link below, as it will give a clearer picture to the overall makeup of this problem.
CodeSandboxLink
I'm making a nested form builder that is dynamic.
This is the current project I'm working on: https://codesandbox.io/s/ava-dynamic-react-hook-form-ivgt40?file=/src/App.js
The issue I'm having is that when I swap or delete Groups, the values within the Group aren't properly set.
I believe it is due to the {...register()} and the altering ref on my Input Components, but I am not certain.
The form data.js file in the linked project at the top contains the form structure I wish to render.
list: {
id,
label,
control: "list" // identifier that this element is a list
creatable: boolean,
items: [
[elements], // group 1
[elements], // group 2
]
}
input: {
id,
label,
control: "input" // identifier that this element is a input
type: "text" // type of input
defaultValue,
rules: {
required: {
value: boolean,
message: string,
}
}
}
State management
You seem to be mixing places to that are keeping tracking of state.
You basicly have 2 types of state
State of the form values | this is stored in the react-hook-form
State of the sort order of form inputs | this is stored in your Element.jsx
By mixing these, you have to manually make sure they stay in sync
react-hook-form
When you're using react-hook-form. you're entrusting some state management to that hook.
They provide an API through register to keep track of the fields and values for the form.
Register takes a name as the first param.
This is the only handle you have for the form to identify fields.
A new name would be a new fields.
the problem
You're not keeping things in sync.
Changing the order will do a couple of things:
The current elementIdPath is something like Emailprofile.0.notifications.0.email.0.email-abcd123
Emailprofile.0.notifications.0.email.0.email-abcd123 is passed as name to the register.
click on the change order button
handleSwapListElements runs and changes the element via setElementItem
the elementIdPath changes: Emailprofile.0.notifications.0.email.1.email-abcd123 (notice index change from 0 to 1)
New name is registerd and treated as a new field
the old field is also still registered and in state. clicking submit will also have the old name and value.
The fix
Keep the sort order and values in sync. You'll have to decide yourself how exactly to do that.
either:
don't include sort order or indexes as key in the name.
manually keep things in sync using react-hook-form methods.
Both have their ups and downs.
not including indexes as key
A dynamic form without indexes would require you to attach all that information on the field itself.
Basically you would register a unique key (uuid), and keep track of the uuid path yourself.
IE you register to properties.:
{
"abcd123-value" : "some#email.de",
"abcd123-path" : "Emailprofile.0.notifications.0.email.0.email-abcd123" // this would not be shown to the user, but still exposed to the form, in the submit you would manually combine the information again.
}
manually keep things in sync using react-hook-form methods.
Whenever you change the name of a field, you would get the current value.
unregister the old name and set the value on the new name.
react-hook-form exposes a method from the hook for that.
unregister
and setValue
If it where up to me, and seeing your code.I would probably try to go in this direction as opposed to refactoring to things to remove the indexes from name.
sandbox
this sandbox with modifications should paint the picture.
It will not properly work yet for the base elements, but the nodes at profile.0.notification.0.email should function.
I'm using mui-datatable to implement table in my app. I've every feature I need up and running, and I'm using server side data and pagination.
The problem is that I need to persist selection of rows when the user change the current page.
I can store the ids of the rows that where selected in an external array using onRowSelected.. but I'm not sure how to make the table render those rows as selected when user changes the page.
Bare in mind i'm using server side data, so the idea would be that in page 1, when I select row 1, a take the id of that record and add it to the array of selected ids. Then I need to check if the ids of rows that are currently displayed in the page are included in the selected array, and if so then check it as selected in the table. That way when I change the page, the same logic would run and all rows would be cleared since none of the row in the new page are selected.. I think you get the point.
I dont know where should i check if the row's id is included y my selected array and if so, how to check it in the datatable.
Thanks in advance for the help.
You can wrap your entire MUI datatable in another component which maintains the state of all selected rows
I'm stupid... I just needed some sleep xD
My problem was solved once I realized that I just needed to pass the rowsSelected option like this:
rowsSelected: this.state.pictures.filter(p=>this.state.selectedIds.includes(p.id)).map((p,i)=>i)
where this.state.picture will change when the user changes the page and rowsSelected will also changed.
Never mind... It's a rookie mistake.
In the ReactJS page of Reconciliation, there are two examples:
an example of the issues that can be caused by using indexes as keys
an updated version of the same example showing how not using indexes as keys will fix these reordering, sorting, and prepending issues
As of Feb 18, 2020, there is no telling of how to reproduce the issue on the page. I tried clicking on "Add New to Start" or "to End" a few times and reorder the list, and they seemed to work fine. Only later I found out you need to enter some text into the box, and then just "Add New to End", and do it three times, and reorder the list.
In the first example, the text in the input box were not re-ordered. In the second example, the text in the input box were re-ordered as expected.
The two programs differ by using
<ToDo key={index} {...todo} />
vs
<ToDo key={todo.id} {...todo} />
There are also some slightly re-ordering of code and using todoCounter vs toDoCounter (capital D) between the two versions, which I wonder why and the React team might fix it later. But you can modify the first version from key={index} to key={todo.id} and you can also see the problem solved.
But then when I looked into the code, the input box doesn't actually add the text data to the state property list (an array). Only id and createdAt are added per new entry to list.
So while we can say using key={todo.id} fixed the problem, what was causing the problem in the first place?
You can say that the id and createdAt are sorted correctly. What is not sorted correctly are the input boxes... but according to the rule of reconciliation to decide whether to refresh actual DOM elements on the page, the new Virtual DOM subtree is compared to the previous virtual DOM subtree.
Now the "property" of the input box value is not part of the virtual DOM, unless if React actually silently puts the value into the virtual DOM.
So when "diff'ing" recursively, React should think the input boxes are all the same. So is this how it worked: if we used the key={index}, now React diff'ed each column in the current virtual DOM subtree with the previous one, and see that the "ID" and "createAt" cells are different, therefore forcing a refresh to the actual DOM. React saw the input boxes all the same and didn't bother to force a refresh to the actual DOM.
However, if we use key={todo.id}, now React will think, the whole row is different, because the row "id" has changed. So React will force a refresh to the actual DOM for the whole row, including the input box.
So we can say, this bug occur only when some data is not in the virtual DOM subtree... which is rare, such as in this current case. In other cases, all the data would be given out by the render() of class component or the return of function component, and therefore, able to tell ReactJS that, "yes, force a refresh to the actual DOM".
Is this really how it worked?
Using index as keys is very dangerous and is highly advised against.
For example, if you delete an item from the middle of the list, another item will take its place, React needs a unique key to recognize a DOM element as an individual/independent element and not something that is temporary or useless.
Your list is being sorted as intended, you can see that the ID column is sorted accurately.
But in your case, React doesn't know which todo belongs to which ID, indexes are not todo list ID, they're numbers that were generated in order.
Imagine this
Todos:
id 1
todo: "take out the trash"
id 2
todo: "make dinner"
if you sort your todos by the shortest string
it will end up like this:
2
1
the normal order is:
1
2
No matter how you sort your list and change its order, if you tell React to use indexes, it will always render it like this:
1
2
3
4
...etc
If you use object IDs, React will keep track of the elements and will connect the keys to the IDs, then render your elements based on the order of your sorted IDs.
I'm having some issues when using shortid or any other unique uid generator. The moment I use shortid.generate() as key in a table, the anchor point of my Material UI Popover is thrown to its default position rather than appearing where the button is.
Here's a sandbox! - try removing/adding back shortid.generate() from line 72.
I even tried uniqueId from lodash and the same thing happens - not using a key does render the dialog on the right place though. I even changed versions of Material UI/React and nothing happened.
Any ideas?
Thanks!
EDIT - I usually use item.uid as key since I always fetch items from a service, but if I just created the object, item.uid is undefined - what I did until now was to set item.uid = shortid.generate() (a temporary uid) when I create the object and then just leave <TableRow key={item.uid}> as is. But then I have to remove the temporary uid before I save the object.
You should never use random ids as keys (at least not random ids generated during render).
When your state changes (e.g. due to handleClick) your table rows will all be re-rendered with new keys. This means that instead of a simple re-render, all of the table rows will be unmounted and new DOM elements mounted. The anchorEl in your state will point at an element that has been removed from the DOM, so its position cannot be determined.
If you don’t have unique keys related to your data, then just use an index as a key so that it is at least stable across renders (so long as you aren’t adding/removing rows). Another option would be to generate the unique ids when creating your data instead of during rendering.