When we are rendering data in React we often grab an array and do a .map() to write out our interface. The inclusion of instructional statements i...
For further actions, you may consider blocking this person and/or reporting abuse
React lists without map and still uses map but hidden deep inside after refactoring ;)
You repeat list is still rerendered as whole when you add or remove elements. You can see it in devtools react profiler.
Each
RenderItem
is rerendered becauseremove
prop change every time, you can fix it by wrappingremove
function withuseCallback
. ButRenderItem
will still be rerendered in other cases because parent compontent rerenders each time. This can be fixed by usingmemo
onRenderItem
and removingindex
prop fromitem.type
because it changes every time and causes rerenders on children component.After this, adding new element will render only one
RenderItem
and removing item will just remove single component.Cheers
Well you don't use a .map when you make this list was my point ;)
Good point on the
remove
- I'd do that by using auseCallback
if I'd remembered lol:The
index
property is important for many things that require sorting etc and while the component will be called when you update the list it then shouldn't be creating a new element if the index stays stable (as I'm inserting at the top of the list that isn't likely)I noticed you set the length of the array. What's the time complexity of doing that to truncate an array? Is it O(1) and the extra elements get garbage collected at a later time?
Thanks!
A very good question, I researched other people's array truncation jsperfs and tried a couple of my own, it ended up being the fastest way at the time to truncate. I'd expect the V8 engine considers the out of range elements to be unreachable and available for GC but possibly doesn't consider the pointer array to be resized - it's a guess though, I suppose you'd have to look at the implementation detail in the engine to be sure and I didn't do that.
One more thought on this I had was if the garbage collector needs to iterate over each extra element to remove it from memory or could it somehow do a bulk delete of the extra elements? If it has to iterate then it would be the same as array.slice with O(n - m) where n = previous length, m = new shorter length. But if it can do a bulk delete it would be good. Not sure though as I don't know this much about the GC, do you have more info or could point me in the right direction?
appreciate the response thanks!
In theory this is great. It addresses a problem in React I hated for a very long time. However in practise this is too complex I feel. What you did was great and the first step is very important however the second step is too complex. Try having a Junior understand that without needing to explain it. The cons of complexity far outweigh the pros of micro optimisation here. This also turns JSX in React into a templating language which is something React is not made for. Not because it isn't a good idea but because it isn't the convention and therefore requires training. Again this is a great concept but too complex for production I feel.
I do see your point and I think the code you use on a project should be balanced by the architects controlling it.
Our main app uses this and a
<VirtualRepeat>
that has the same signature but lets you specify a number of visible items and it's nice because everyone knows them and can swap between them as necessary - however - it does require training and would not work well in many circumstances. Also we use the version that does<Repeat list={blah} item={<SomeItem blahblah={doSomething}/>} />
over the one that uses it as a child.Personally I'd prefer using normal JavaScript instead of using a magical wrapper that does exactly the same as a simple
.map
.I do build wrappers where it makes sense, but often I'd rather use the following strategy to clean up my code.
Here is a simple example, where I go from a messy implementation:
To a more cleaner implementation:
In my opinion, first is easier to read because of not so many abstractions. I'd prefer to create extra abstractions only when implementation becomes big. Then it becomes simpler to understand which parts of code are worth separate abstractions and which are not.
Agreed, when it's small, totally. I see them bigger than the surrounding component, then I think it should be separated.
I love abstractions, but only when it reduces boilerplate and effort to create. The amount of code generated in order to abstract away map makes the effort not that valuable. I do say it's a good learning exercise, but I wouldn't do this in all projects since you introduce more components into the reconciler, and you're needing to spread a bunch of props in order to get the desired result, which means more overhead.
Maybe I'm missing something, but isn't the effect only adding one parent component? All the others would need to be added anyway and have their own props object returned by the
createElement()
. I've just moved the props setting (and admittedly probably created one more object along the way).If you wanted to be pure and just make the one props object that every entry in the map needs you could write the output like this:
In general, doing something like this is quite bold. I respect you for it.
But there are quite many disadvantages in this approach. There are some:
Props like
item
andindex
are passed to components implicitly. I personally prefer when everything is explicit. Also in most cases we don't needindex
and thisindex
can cause unnecessary updates in some cases (for example inserting item to the middle). It can be fixed by get rid of cloning react elements and using render functions.Also, this cloning React elements with spread operator is quite going too deep into react internal implementation. It would be better to use clone element
I noticed, that
getKey
relay on references of objects. That means that when reference changes, then key also changes. When key changes, component will be unmounted and mounted again. So it would be better just to havekeyFn
as required argument.Repeat
can be used only with react elements which doesn't havechildren
inside them.The last example with so many mutations inside looks really dangerous
Sure, I get your concerns.
A short rebuttal:
Repeat can be used on items with inner components, children is just another prop and it works fine
Index, sure can go any way on this, I mostly use this on lists that allow sorting, and the sortable HOC requires index so I just leave it in there. You could easily make it work differently by having explicit props for sure.
getKey is just a default implementation that works for object items that don't change, you can supply another keyFn and in my main app this would always be
v=>v.id || v._id
- but this is of course implementation specific.About children I just mean, that it is not possible to do something like this:
Because with
Repeat
we don't have access to item itself. Allitem
based rendering always have to be inside a component and external children of the component must not rely on theitem
.In some cases we just don't need a component
Right, right sorry. I should have though that through :)
Yeah you can't do that, unless you use a version which takes a function as a child, but then if it's this case... well I'd just probably do the
items.map
as you point out. My project's version does have support for a function child but really the semantics are then using{
}
so I don't bother and just use themap
.The real point was when you need to do a sub component because you need hooks in the wrapper. I'd prefer this laid out in the template version (because frequently in our code base we'd swap from
<Repeat>
to<VirtualRepeat>
which has the same signature, but virtualises the elements.‘Repeat’ is a good abstraction, which hides some complexity behind it and increases signal/noise ratio.
A perfect example of DSL in the form of Component 👍
Having configurable getKey function with automated defaults is 🤟 as well, because those WeakMap Magic’s are not required if you a real key to use.
How about this approach? github.com/leebyron/react-loops
Thanks, not seen that one!
On first scan, it's using a render function for items - which is fine, but I think it still has cognitive dissonance as you reason with the parameters of the render function and figure out the
key
on the item. I'm in JSX, I'm about to write some more JSX, I don't need this render function is my personal preference.My full implementation of this allows for that approach (and multiple functional children if you like!) but if I look at the code where we use our function, it never actually happens. All we ever use is the
item={<SomeRenderer/>}
because it covers off all of our requirements and the code scans better.Repeat with auto keying and debug fall back to JSON is 29 lines of code. I wouldn't make an npm package out of that :) I'd write a Dev article LOL!
Oh, I basically understand, thanks for your detailed reply.
For the approach without render function, I also have seen this: github.com/AlexGilleran/jsx-contro...
But it has a trouble problem that in TS: the
item
needs to define a variable separately to match the TS type.I feel that the approach of
react-loops
, which maybe has a higher recognition, because it uses therender props pattern
commonly used in React. For example,solidjs
also uses this<For>
design: solidjs.com/docs/latest/api#%3Cfor%3EAfter reading your article, I think it's certainly useful. I just feel that at first glance, there is a lot of code, which is a little complex for people who see it for the first time, perhaps it would be better to have a simplified version. Thanks for sharing~
I mean, you would have the actual function in a utilities file and it's only a few lines of code. It works fine in TS. Here's an example of Repeat written that way.
Seriously that's cool, and I see you define function after return of component markup. I might try it work. Let's see what I get from my manager, lol.
Yeah that's a "thing", I always do this because I like the main purpose - the body in other words - of a routines to be first. Then utilities it uses next. Doesn't work in TS with linting (because TS hates me!)
Make things complicated.
.map
is clear. If your list item JSX has many, you should consider extract an item component.:) Your choice, but if you look at the next part which takes the principle to virtualised windows and lists then (as that article mentions) it becomes a lot more than sugar.
nice exercise but I do not think that the point of react is to write another framework with it :)
Cheers