-
Notifications
You must be signed in to change notification settings - Fork 46
Reorder actions #64
base: master
Are you sure you want to change the base?
Reorder actions #64
Conversation
2d55beb
to
d016e45
Compare
Looks great! I'm going to review it (and rest of stuff) this weekend. Consider it my new year's resolution :) |
Just make sure to update |
@@ -1,5 +1,6 @@ | |||
import React, { Component } from 'react'; | |||
import ReactDOM from 'react-dom'; | |||
import dragula from 'react-dragula'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have mixed feelings about this lib. It's simple and it works, which is great, but it's not so react-friendly (and it uses css hardcoded classes). I would suggest at least move this with all the dirty stuff in a separate component, if it's possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried react-sortable-hoc
first, but it requires too much changes here, and there were some issues. Didn't notice any issues with react-dragula
. It allows us easy to switch draggable feature on/off and also we will be able to duplicate actions in future.
Those classes are added only while dragging. We could just set draggableActions
prop to false
by default, so it will have no impact when not enabled.
However, if you think this feature is not needed here, we could just add a prop to pass a custom ActionList
component. It would also allow for better customizations. For example, I was planning to add props to hide buttons since we'll not need then in the extension.
We could just use a fork for the extension if you think the purposes are different and you don't want to add extra complexity here. For example, including react-split-pane
per #62, will also use global classes, and even worse prefixed. It is not a problem for the extension, but could be for Redux DevTools. Let me know what you think would be the best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind having this feature, but it kinda forces us to use native DOM (including data
attributes) and it concerns me.
Have you tried react-dnd
, BTW? I know, it looks a bit scary at first, but it's pretty good, Dan did a great job there :)
http://gaearon.github.io/react-dnd/examples-sortable-simple.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was using it here, but those decorators add complexity as well as react-sortable-hoc
. We need 2 different ActionList
components - one draggable (with the decorator) and another one not (when having draggableActions
propfalse
). Also we need to update local state to drop the element before getting the data from the client part (which takes time in case of the extension). I see your point against using the DOM here, though it does its job. Not sure I'll have time in the next days for it (as other issues and 3.0
are waiting). Maybe someone else could continue the work here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need 2 different ActionList components - one draggable (with the decorator) and another one not (when having draggableActions prop false)
Or you can just use DragSource.canDrag
:)
It's ok to use react-dragula
for now, it just would be nice to have it separated, so it could be easily replaced later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, ActionList
is a bit more than just a plain list of rows, so I would introduce <DraggableList />
instead of <div {...styling('actionListRows')} ref='rows'>
that would be responsible just for dragging, so it would be like:
<DraggableList onReorderAction={...} ...>
{ids.map(id => <DraggableListRow ... ><ActionListRow ... /></DraggableListRow>}
</DraggableList>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. In this case I'd pick react-sortable-hoc
, which simplifies it and also have some killer features like locking dragging inside the parent container or axis.
However, the reason I picked Dragula is not to implement that. Action list items are the components which affect the performance the most. So, due to maxAge
, we can have lots of such components removed and added per second.
Taking into account that this feature would be used occasionally by less than 5% of users, we either find another way not to introduce any complexity here or not implement it at all. A solution would be to have a button to enable dragging (so we'll rerender ActionListRow
s with added DraggableListRow
), but, since Dragula offers it without any costs, I don't think it is worth to add changes to the UI.
Maybe I missed something, feel free to amend my pr.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, react-sortable-hoc
looks much better. Actually, it would be interesting to measure it - is there really noticeable difference in performance for high-loaded actions.
By the way, it might be useful to utilize something like react-virtualized for this list.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about react-virtualized
too, but it wouldn't help much, at least for the extension where maxAge
is 50
by default, and, anyway, we shouldn't hide (virtualize) the new added action rows. The perf waste is due to creating new elements for every dispatched action. This is the part where React is very slow, and we should also take into consideration that unlike the extension, for vanilla Redux DevTools, React is in dev mode, making it even slower.
Anyway, Inspector is twice faster than LogMonitor. The fastest is Chart monitor, despite of lots of d3 animations there.
I added recently autoincrement
button to the demo and, as expected, it was crashing the extension. Surprisingly, it was mostly due to React, not to serialization. Just bouncing the data sent to the extension solved the problem.
A benchmark would be rather useful indeed, so we could do it for every new feature to see the cost it adds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a benchmark. It starts from an empty action list and adds actions one by one.
Before: 29.16 ops/sec ±25.13% (18 runs sampled)
.
After adding react-dnd
's DragDropContext(HTML5Backend)
and DragSource
hocs: 25.05 ops/sec ±25.87% (18 runs sampled)
.
When starting not from an empty list, but having already 500 actions, it's 4.98 ops/sec ±4.29%
(without hocs) and 3.64 ops/sec ±4.39%
(with react-dnd
). But we're usually avoiding such bad perf with maxAge
. Also note that I'm benchmarking production build, since it's not recommended to benchmark React in development and we're not using it for the extension. Without the extension we're running on dev environment and the perf should be worse.
Obviously, Dragula has no impact, as nothing is done on component update. Unfortunately, I wasn't able to get react-sortable-hoc
working on jsdom
(it fails after calling findDOMNode
inside the lib), but it shouldn't be much different from react-dnd
, as there's lot of work inside the hocs on every update.
If the perf waste would be for a core feature, it could be negotiable, but not for this one. It's not something we'd use frequently, but nice to have (I just got inspired by Dan's workflow). We would also add the ability to duplicate actions (when dragging an action holding Alt
key), which should be more popular.
Feel free to make changes in the pr when you have some time. It's not in a hurry, we're using a fork meanwhile.
UPDATE: For the sake of the experiment, I added also a benchmark for updates when using maxAge (so a row is added and the first one is removed).
Without hocs:
- Insert x 31.20 ops/sec ±27.06% (17 runs sampled)
- Update x 15.74 ops/sec ±1.61% (42 runs sampled)
With hocs:
- Insert x 26.61 ops/sec ±27.72% (16 runs sampled)
- Update x 13.64 ops/sec ±2.44% (37 runs sampled)
@@ -19,6 +20,31 @@ export default class ActionList extends Component { | |||
|
|||
componentDidMount() { | |||
this.scrollToBottom(true); | |||
if (!this.props.draggableActions) return; | |||
const container = ReactDOM.findDOMNode(this.refs.rows); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to use callback ref instead of string (see https://github.com/bevacqua/react-dragula#example-using-refs-es2015-syntax), as string refs are not recommended recently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but it was introduced before and used in other places. Better let's keep it for another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
src/ActionList.jsx
Outdated
), | ||
moves: (el, source, handle) => ( | ||
parseInt(el.getAttribute('data-id')) && | ||
handle.className.indexOf('selectorButton') !== 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean !== -1
?
Also, using className here looks unreliable. Can we use data
attribute or something? (which is also not great, but better)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're returning false
here, not to drag through the buttons. It could be === -1
, but it doesn't guarantee that it's a substring of another class. On the other hand, it implies that this class will be always the first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to a better checking here.
Implementation of zalmoxisus/redux-devtools-instrument#13.
Here's how it works:
Or a more practical example: