Animate React rearranging of list items - javascript

I have an array of JSON objects that are used to render a list of elements:
mydata.map(thing => {
return <SomeComponent key={thing._id} />
});
Each rendered item has up/down arrows, and if the user clicks the up arrow, for example, the item will be moved up the list. On the back end, the items in the array are simply swapped.
How can I make this process animate so it's easier for the user to see what the result of their action was? I was looking into react-motion, but it seems to work based on modifying CSS/styles. I was hoping there would be something that uses React's key property to determine unique elements and handle movement based on that.
Any suggestions?

React-flip-move is a wonderful component which allows you to animate children components. There is an example animation flipping values on a list too.

Related

Issues on animating a conditionally rendered component using React-spring

I am new with react-spring. I am having trouble animating a component whenever it unmounts. I have a simple card with an onClick handler that's responsible for conditionally displaying my Overlay component. The animation works fine when mounting (from & enter works), but when closing the overlay, the component just disappears without animation (leave does not work). I suspect it's because of the conditional rendering of the component but I've been struggling for hours trying to find a solution for this one. Any help would be appreciated!
My current code: https://codesandbox.io/s/dry-leftpad-h3vmv
What I'm trying to achieve: https://codesandbox.io/s/048079xzw
P.S. The latter is using mauerwerk's lib. I don't want to use that.
What you were missing is this:
return expand.map(({ item, props, key }) => (
item && <animated.div
// ...etc
When you're controlling the mounting of a single component with useTransition, you need to conditionally render it based on the item being passed. In your case, when it's false it won't render (which will unmount if already mounted) and when it's true it will render (mount if unmounted).
Here's a working sandbox forked from yours: https://codesandbox.io/s/infallible-agnesi-cty5g.
A little more info
The first argument to useTransition is the list you want to transition. That watches for changes and sends back an array mapped with each item, a key and a style object (props) based on whether the item is truthy (entering) or falsy (leaving). So for a transition that mounts/unmounts a single element, conditionally rendering based on the truthiness of the item is key.
Check out the examples again here and you'll see the differences between transitioning a list, a toggle between two elements, and a single item.
For a list, no need to check for the existence of the item because the array changes.
For toggling between two elements, you use the truthiness of item to determine which element to render.
For a single element, item determines whether to render at all. This means it won't mount initially when you default to false, and will make sure you don't render 2 items whenever your isActive value changes.

Vue Draggable not working with filterBy?

I am using vue draggable component to drag and drop objects from one div to another. I am also using filterBy filtering the v-model. The problem is when I filter(and for example get 1 result) and try to drag and drop the item the very first from all items is dropped and not the dragged item.
Dragging from:
<draggable :list="available" class="draggable" :options=" group:'stuff'}">
<div v-for="people in filterBy(available, filter_available, 'name')" class="list-item">
<img :src="image" height="20"> {{ name }}
</div>
</draggable>
Dropping to:
<draggable :list="drop" class="draggable" :options="{group:'stuff'}">
<div v-for="people in filterBy(drop, filter_doppred, 'name')" class="list-item">
<img :src=image" height="20"> {{ name }}
</div>
</draggable>
On it's own both features are working fine. However, when filtering the results, it changes the index of the elements and when moving from one list to another - I move a different item, not the one dragged.
Any Ideas?
Well, you have to pass the same list to the draggable component that you use for the v-for - both need the filtered result.
You currently use a method to do the filter operation. You would have t call this method twice to get the same result to both places.
You should instead use a computed property to avoid doing the same operation twice.
Edit: the draggable component will mutate the provided list, which will not affect your source data when you use a computed property.
After looking at the docs for vue-draggable, you should probably use the changeevent provided by the component (https://github.com/SortableJS/Vue.Draggable#events) to update your source data.
I can't give you a concrete example because I don't know what your logic is to determine the new position of the moved element in the unfiltered source list.
Mostly I agree with #LinusBorg, with some minor differences:
1) draggable list props and v-for must use a collection
2) Create a different array for the filtered array. A computed property won´t work as draggable will try to change it. You can use a data field and use watch if the master array may change.
3) Listen the change event to propagate change on the master array.
I think that's happening because SortableJS uses indexes to get the currently dragged element.
// Sortable.js:341
// Get the index of the dragged element within its parent
startIndex = _index(target, options.draggable);
I have a similar case:
several sets of drag'n'drop groups should share the same list of items, and the items cannot repeat across sets.
I think I'll end up with adding/removing the elements manually.
I added a property(visible) to my object, when the filter change I set the property to true if is still visible, to false if not. Based on the property i hide or show the element to avoid create a new array with filterBy, filterBy is the reason the index changes and does not match with the original array (cause it creates a new one).

Remove Item from Paper.js Group

Paper.js has a clear way to add an Item to a Group using addChild(item). However, there does not seem to be a clear way to remove an Item from a Group without also removing that item itself from the view.
Groups have a children property, but according to the documentation, it should not be mutated:
The children array should not be modified directly using array functions. To remove single items from the children list, use item.remove(), to remove all items from the children list, use item.removeChildren(). To add items to the children list, use item.addChild(item) or item.insertChild(index, item).
So each item has a remove() method, but this not only removes it from the Group but also from the display.
How can I remove an Item from a Group, only dissociating it with the Group and not removing it from the display? Is there a cleaner way to do it than this?
item.remove();
paper.project.activeLayer.addChild(item);
Your approach
item.remove();
paper.project.activeLayer.addChild(item);
is how it should be done. Unless you call paper.view.update() it's not going to re-render the canvas in between the two calls, so there is little cost in making the extra function call.

Highlight last inserted document in Meteor

I have form and list of objects at the same page. When I insert a new row, it is not very easy to see where the newly inserted row is placed. Therefore, I thought I could color/highlight the newly inserted row (and perhaps remove the highlight after a few seconds).
How can I do this? I think a way to do this could be using a method on the server which returns the inserted id (return Collection.insert(doc);) and on the client use a callback with
Meteor.call('insertDoc', function(err,result) {
// do something with result
});
I think I can use a reactive-var to save the id of the last inserted row and in the loop highlight the row with
{{#each docs}}
<li class="{{isActive}}">{{name}}</li>
{{/each}}
and have a helper to return active if this._id equals the reactive var with the last inserted id.
But is this the best way to do it? How can I remove the color after some seconds? I have seen such behaviour on many pages but I cannot find any tutorials/code snippets to achieve this.
I wrote a package that uses Meteor's UI hooks to fade items in and out of a list as they are added and removed, to help users maintain context as data changes:
https://github.com/mizzao/meteor-animated-each
There is a demo at http://animated-each.meteor.com/. You can see that as items are added and removed, they are faded in and out. If items are inserted off the screen, the visible area does not scroll.
This isn't doing exactly what you want, but you can use the same idea to highlight items as they appear as well, as opposed to the simple fade in.
Note that all of this happens at the UI rendering level - not the template/code level. The UI hooks are also not well documented right now, but they've been around for a while.
I don't know if your method is the best, but that's how I'd go about doing it.
As for the animation, I'd use a CSS3 animation. Plenty to choose from ( https://developer.mozilla.org/en-US/docs/Web/CSS/animation ), and you can easily make them fade to the standard color. The animation would also only be applied to the last inserted item (because of the way you did it, only the last item would have the "active" class)

Transitioning between models on a ng-repeat

I'm building a directive (Angular 1.2) that will toggle between displaying two different lists - think of them as a list of "trending" items on the site and a list of items the user is "following". So we have a toggle that allows the user to choose which list is being displayed, with an ng-repeat below it showing the items.
There will be a good deal of overlap between these lists, and when the user toggles from one list to the other, I'd like items that are contained in both to transition from their places on the "outgoing" list to their places on the new one, rather than disappearing and reappearing.
My question isn't about how to achieve the actual animations (we're using ngAnimate), but about how I should structure the controller/data to. I'm thinking about my directive controller having a trendingList and a followingList (that contain the actual data items), and an activeList that points to whichever of the two are currently being displayed. So the ng-repeat is actually on activeList, and toggling is essentially:
$scope.toggleMode = function(){
if ($scope.mode == 'trending')
$scope.mode = following;
$scope.activeList = followingList;
else {/*the inverse...*/}
}
Is that the most reasonable approach? If so, how do I ensure that angular recognizes the equality of objects present in both lists?
Or is there an easier/cleaner way to do this?
From what I understand, angular isn't tracking the equality of objects in the list it is repeating. The animations are triggered when the DOM is manipulated, not when the data changes (though data changes will change the DOM). As to what you should do, I don't have experience with this problem so hopefully somebody can point you in the right direction!

Categories

Resources