I have the following data structured in this way:
type GroupType = {
id: number // Always unique
type: "group"
groups: (GroupType | OtherType)[]
}
type OtherType = {
id: number // Always unique
type: "other"
text: string
}
The id
s in both of these types are always unique, even between the GroupType
and the OtherType
.
I have the following component:
type Props = {
group: GroupType
}
export default function Group({ group }: Props) {
const elements = group.groups.map(ele => {
if (ele.type === "other") return <p key={ele.id}>{ele.text}</p>
else return <Group key={ele.id} group={ele} />
})
return (
<div>
{elements}
</div>
)
}
Here is the situation, when the data (GroupType.groups
) is rearranged, it reloads all components from the beginning (which is okay), but when I move one of them from one GroupType.groups
to another GroupType.groups
, it creates new components from the beginning because the elements in a list are only related to each other if they are siblings (differentiating them by the key
). How do I make them related? (Whether using the key
or not). The id
key is always unique among all other objects regardless where each object is in the nesting.
Example
Let’s say we have the following data:
const group: GroupType = {
id: 0,
type: "group",
groups: [
{
id: 1,
type: "other",
text: "text 1",
},
{
id: 2,
type: "group",
groups: [
{
id: 3,
type: "other",
text: "text 2",
},
{
id: 4,
text: "text 3",
}
]
},
{
id: 5,
type: "other",
text: "text 2",
},
],
}
When the above is rendered as a component (<Group group={group}>
), the following component structure is created:
App
Group (key=0)
p (key=1)
Group (key=2)
p (key=3)
p (key=4)
p (key=5)
Now, keys 1, 2, and 5 are siblings, which means that if I rearrange them, react will also rearrange them and not create an entirely new component. Keys 3 and 4 are siblings too, but keys 1 and 3 are NOT siblings, which mean that if I rearrange the keys and, for example, move key 3 to be before key 2
App
Group (key=0)
p (key=1)
p (key=3)
Group (key=2)
p (key=4)
p (key=5)
react will create a new component for it because it’s a new key between these siblings (1, 2, and 5). This is my problem. I do not want react to treat it as a new component, but rather has a siblings (if possible) of them.
Why do I need this?
On my website, a user can navigate the website with just the keyboard (for accessibility reasons). I have some components that move based on the keys pressed by the user and what the user selects (by focus-visible). When a user moves a component, it deselects that component, which is annoying because now they have to move through the components to get to the right component and move it again.
2
Answers
if u want to relate them with key then u can use index of each element in key’s value, this can remove ur dependency on id cause u said id is unique
Short answer
As we know, the key is with respect to the container. Therefore even if there is no change in keys, a change in container will lead to recreating the same component.
Detailed answer
The above point puts the emphasis on the container. On the other side, a recursive rendering as the below code does, has a significant impact on its resulting containers.
The console log in the code will help us to know that with the given data, this component is rendered twice with two separate data. It means the below single declaration rendered in two separate times. It is true since there is a recursive call for the nested group with id 2 in addition to the initial call for the id 0.
Let us view the console log generated:
Observation
These are the two distinct arrays React has created for us while rendering the component. In this case, the two arrays containing the items 1,2,5 and items 3,4 respectively.
Whenever there is a change in the data resulting a change in the containing array, react will remove the component from the container it has been changed from, and will add the component to the container it has been changed to. This is the reason for the issue we have been facing in this post while moving an object from one group to another.
Coming back to the point again, we face this issue since internally there are separate arrays for each nested group.
One possible solution
One solution may be to render in a way that it does not produce separate containers with respect to each group. However, this approach will necessitate a review on the recursive render. We need to find a way to render it so that the entire items contained in a single array, so that we can move items as we will. And react will not either remove or add components.
The following sample code demoes two things:
a. The existing render and the issue we now face.
b. Render without recursion, so that the issue may be addressed.
App.js
Test run
On loading the app
Test to move the component Text 3 in Group 2 to Group 0 – using the recursive rendering
After clicking the button "move text 3 from Group 2 to Group 0".
Observation
The component has been moved from Group 2 to Group 0 as we can verify from the labels, however, the input has been lost. It means, React has removed the component from Group 2 and newly added it to Group 0.
We shall do the same test with rendering without recursion
After clicking the button "move text 3 from Group 2 to Group 0".
Observation
The component has been moved by retaining the input. It means, React has neither removed nor added it.
Therefore the point to take note may this:
Components with keys will retain retain states as long as its container is not changing.
Aside :
The component without keys will also retain states as longs its position in the container is not changing.
Note:
The sole objective of the proposed solution is not to say do not use recursive rendering and use imperative way as the sample code does. The sole objective here is to make it clear that – Container has great significance in retaining states.
Citations:
Is it possible to traverse object in JavaScript in non-recursive way?
Option 2: Resetting state with a key