What does getHandlerId() do and how to use it? - react-dnd

Some of the react-dnd examples use a getHandlerId() method.
For example in the simple example of a sortable list, the Card.tsx function:
Collects a handlerId from the monitor object within the useDrop method
collect(monitor) {
return {
handlerId: monitor.getHandlerId(),
}
},
Returns that as an element of the "collected props"
const [{ handlerId }, drop] = useDrop<
Uses it to initialize an HTML attribute named data-handler-id
<div ref={ref} style={{ ...style, opacity }} data-handler-id={handlerId}>
What is this Id and why is it used?
What uses the data-handler-id attribute?
I'd expect to see getHandlerId() described in the API documentation as a method of the DropTargetMonitor (but it isn't).

I didn't dive deep into it but for me this information was enough to continue using it:
If you remove this data-handler-id, everything continue working but with some issues (item sometimes flickers, it doesn't go to another place as smoothly as it does with data-handler-id)
Here is an open issue https://github.com/react-dnd/react-dnd/issues/2621 about low performance, and this comment suggests to use handler id: https://github.com/react-dnd/react-dnd/issues/2621#issuecomment-847316022
As you can see in code https://github.com/react-dnd/react-dnd/search?q=handlerId&type=code, handler id is using for proper definition of drop item so it seems better to use it even if you don't have a lot of elements.

Related

vue does not recover from me specifying a non existing location for v-model

When I have a textarea like
<textarea v-model="foo.abc.text"></textarea>
and either foo or foo.abc does not exist yet then
vue removes either parts of the DOM or is giving me a blank page.
It does never recover.
That alone is annoying, regardless of if I am using a debug version of vue or not.
If I try to use an approach that I have been advised to use earlier like
<textarea v-model="foo?.abc?.text"></textarea>
then I am still out of luck, I presume that I get a "rvalue" using those question marks and what I need rather is a variable location.
How do I, with as little trickery as possible, allow v-model to exist later on even if it doesnt exist now (late binding)?
Just shape your data accordingly and initialize it with empty values:
data(){
return {
foo: {
abc: {
text: ''
}
}
}
}
You can later populate it e.g. with the result of api call, but it's still better to initialize data properly
I would suggest going the :value + #input way. It allow more control over the input model, and does not require hiding it.
<textarea :value="!!foo && foo.abc.text" #input="(val) => !!foo && (foo.abc.text = val)" />
You can even hook in a validator:
<textarea
:value="!!foo && foo.abc.text"
#input="(val) => !!foo && (foo.abc.text = val)"
:rules="v => !v && 'The object has not been initialised'"
/>
I found a solution I can live with and then I got a comment in the same direction:
Conditionally showing the textarea.
v-if seems to do it but it falls under the "trickery" category, I think (angularjs would be more relaxed).
<textarea v-if="foo!=null" v-model="foo.abc"></textarea>
The symptom to hiding components if something is not all correct is not the best part of vue.js. Better show them and color them red.

Adding multiple class for Swiper Pagination Bullet

I am trying to add a new element class to each of my pagination bullet, and I want to retain the default style of the swiper. So what I did is
pagination={
clickable: true,
bulletClass: `swiper-pagination-bullet ${style['feature-pagination']}`,
}
I was able to get the style of swiper-pagination-bullet and my custom style. However, the other functionalities is not working anymore (e.g. click function, active selection)
I tried to check the code and it looks like the swiper is not currently handling multiple class, since this line of code returns empty since it is only expecting a single class only.
Is there any work around for this? I like to create pull request for this, but I like to ask the community of I am missing in here.
Update
Now it support multiple class with this changes. You can add multiple class by separating them with spaces
pagination={
clickable: true,
bulletClass: `swiper-pagination-bullet ${style['feature-pagination']}`,
}
Old
So I requested an enhancement to Swiper Repository. As of the moment, the pull request to handle bulletClass and bulletActiveClass still haven't accepted.
For the mean time, this is the best workaround for it.
pagination={
clickable: true,
bulletClass: `swiper-pagination-bullet`,
renderBullet: (index, className) => {
return `<span class="${className} feature-pagination"></span>`;
}
}

Object reactivity of complex object

I have an issue with complex object reactivity.
I've read everything I can on stack to find a way to solve it, but nothing works. I've looked at object reactvity and array caveats on vuejs, but not working either.
So I'm asking some help please.
Let me explain the project:
I have 2 columns :
- on the left side, I CRUD my content
- on the right side, I display the results
I have my object, and I'm adding new elements on its "blocks" property (text, images, etc...)
[
{
"uid": 1573224607087,
"animation": "animationName",
"background": {
"bckColor": "#ff55ee",
...
},
"blocks": []
}
]
On click event, I add a new element via this method. Everything is ok, I can CRUD a block.
addBloc(el) {
if (el.type == "text") {
const datasA = {
type: "text",
uid: Date.now(),
slideId: this.pagination.currentPage,
content: el.content,
css: {
color: "#373737",
...
},
...
};
this.slides[this.pagination.currentPage].blocks.push(datasA);
this.$bus.$emit("newElement", datasA);
}
To modify the order of my elements on the display side, I added a drag and drop module to move my block on my DOM tree. Smooth dnd
The problem is, when I drang&drop my element, my object is updated correctly, but the DOM isn't. The dragged element goes back to its initial position.
What is strange, when I try to modify my block (the one I dragged), it modifies the other one.
I'me adding a small video, so you can see what's happening.
Small animation to show you what's going on
I add some more explainations.
I use event bus to communicate between my components, and the right side is using its own object!
I don't know how I can solve this issue.
Tell me if you need more information.
Thank you all !
EDIT 1 :
I added an id to each block to see what happens when I start Drag&Drop. ==> blocks are moving correctly. The problem is not coming from the method onDrop() but from my nested components if I understand well. They don't update. I'm going to search for this new issue.
I've added a new gif to show what's going on.
This is the nested structure
TheSidebar.vue => top container
<Container
:data-index="i"
#drop="onDrop(i,$event)"
:get-child-payload="itemIndex => getChildPayload(i, itemIndex)"
lock-axis="y"
>
<Draggable
v-show="pagination.currentPage === i"
v-for="(input, index) in slides[i].blocks"
:key="index.uid"
:id="'slideBlocksContainer'+index"
class="item"
>
blockId #{{input.uid}}
<AppContainer
v-if="input.type == 'text'"
:blocType="input.type"
:placeholder="input.content"
:id="index"
:slideId="i"
></AppContainer>
</Draggable>
</Container>
Then I have my AppContainer.vue file, which is a top level. In this I have the specific elements of each input type
And I have AppElement.vue file, which is common elements, I can use everywhere
Something like this
TheSidebar
--AppContainer
----AppElement
Know I don't know yet, how to force vue to update AppContainer.vue and AppElement.vue
EDIT 2 :
As suggested in this article I've changed the key of the component and now , when I drag and drop my elements, they stay where they are dropped.
What I see also, is that the AppElement inputs, are related to their own AppContainer. So everything is ok now, but I don't know if it is best practices.
The issue appears to be that the Smooth dnd library you are using is not updating the array of blocks that you are passing to it, it is likely making a copy of the array internally. So when you change the position of the blocks by dragging and dropping, you are not changing your blocks array, just the internal copy.
Looking at the Smooth dnd documentation, if you wanted to access the modified array you could try using the drag-end event handler:
onDragEnd (dragResult) {
const { isSource, payload, willAcceptDrop } = dragResult
}

Sectioning data in a ListView

Say you have a list of People incoming from your API.
[{content: 'John'},
{content: 'Tim'},
{content: 'Harry J. Epstein'}]
And you're looking to put people who are first-name-basis friends (John and Tim) under a section 'Friends' and people who are not (Harry J. Epstein) under 'Contacts'.
Tapping a friend selects them with a blue highlight, but tapping a 'contact' selects them with a red highlight.
Would the proper approach be to take the incoming data from the API, add a type: 'Friend', ... or type: 'Contact', ... around it, and section based on that type with separate a FriendItem and ContactItem class so I can split the highlighting function?
I've got a bunch of just basic ListView code that does this exact approach, but I'm basically looking for the easy way out, like Angulars ng-repeat equivalent.
So what's the React Native version of
var friends = api.getFriends()
var contacts = api.getContacts()
<div ng-repeat="friend in friends" ng-click="highlightFriend()"> ... </div>
<div ng-repeat="contact in contacts" ng-click="highlightContact()"> ... </div>
I'm struggling to understand how to split it. Do I need a FriendsPage, FriendsItem, and ContactsItem? Or put everything into one array in FriendsPage and use a FriendsItem that checks if it's a friend or contact and adds a function separately?
I feel like I'm slightly lost coming from MVC. I've got Redux running too, if there's an easy way using that.
Here is a nice example on how you can create section-dependent rows: https://github.com/spoeck/ListViewExample
The idea is basically to create the data blob properly, which is a bit tricky, and then in your renderRow callback, check the sectionID parameter:
_renderRow(rowData: any, sectionID: any, rowID: number) {
if (sectionID === this.data[0].section) {
return <MyFriends />
} else if (sectionID === this.data[1].section) {
return <MyContacts />
}else{
// ...
}
}
why don't you try SectionList
Use the new FlatList or SectionList component instead. Besides
simplifying the API, the new list components also have significant
performance enhancements, the main one being nearly constant memory
usage for any number of rows.

How to use store.filter / store.find with Ember-Data to implement infinite scrolling?

This was originally posted on discuss.emberjs.com. See:
http://discuss.emberjs.com/t/what-is-the-proper-use-of-store-filter-store-find-for-infinite-scrolling/3798/2
but that site seems to get worse and worse as far as quality of content these days so I'm hoping StackOverflow can rescue me.
Intent: Build a page in ember with ember-data implementing infinite scrolling.
Background Knowledge: Based on the emberjs.com api docs on ember-data, specifically the store.filter and store.find methods ( see: http://emberjs.com/api/data/classes/DS.Store.html#method_filter ) I should be able to set the model hook of a route to the promise of a store filter operation. The response of the promise should be a filtered record array which is a an array of items from the store filtered by a filter function which is suppose to be constantly updated whenever new items are pushed into the store. By combining this with the store.find method which will push items into the store, the filteredRecordArray should automatically update with the new items thus updating the model and resulting in new items showing on the page.
For instance, assume we have a Questions Route, Controller and a model of type Question.
App.QuestionsRoute = Ember.Route.extend({
model: function (urlParams) {
return this.get('store').filter('question', function (q) {
return true;
});
}
});
Then we have a controller with some method that will call store.find, this could be triggered by some event/action whether it be detecting scroll events or the user explicitly clicking to load more, regardless this method would be called to load more questions.
Example:
App.QuestionsController = Ember.ArrayController.extend({
...
loadMore: function (offset) {
return this.get('store').find('question', { skip: currentOffset});
}
...
});
And the template to render the items:
...
{{#each question in controller}}
{{question.title}}
{{/each}}
...
Notice, that with this method we do NOT have to add a function to the store.find promise which explicitly calls this.get('model').pushObjects(questions); In fact, trying to do that once you have already returned a filter record array to the model does not work. Either we manage the content of the model manually, or we let ember-data do the work and I would very much like to let Ember-data do the work.
This is is a very clean API; however, it does not seem to work they way I've written it. Based on the documentation I cannot see anything wrong.
Using the Ember-Inspector tool from chrome I can see that the new questions from the second find call are loaded into the store under the 'question' type but the page does not refresh until I change routes and come back. It seems like the is simply a problem with observers, which made me think that this would be a bug in Ember-Data, but I didn't want to jump to conclusions like that until I asked to see if I'm using Ember-Data as intended.
If someone doesn't know exactly what is wrong but knows how to use store.push/pushMany to recreate this scenario in a jsbin that would also help too. I'm just not familiar with how to use the lower level methods on the store.
Help is much appreciated.
I just made this pattern work for myself, but in the "traditional" way, i.e. without using store.filter().
I managed the "loadMore" part in the router itself :
actions: {
loadMore: function () {
var model = this.controller.get('model'), route = this;
if (!this.get('loading')) {
this.set('loading', true);
this.store.find('question', {offset: model.get('length')}).then(function (records) {
model.addObjects(records);
route.set('loading', false);
});
}
}
}
Since you already tried the traditional way (from what I see in your post on discuss), it seems that the key part is to use addObjects() instead of pushObjects() as you did.
For the records, here is the relevant part of my view to trigger the loadMore action:
didInsertElement: function() {
var controller = this.get('controller');
$(window).on('scroll', function() {
if ($(window).scrollTop() > $(document).height() - ($(window).height()*2)) {
controller.send('loadMore');
}
});
},
willDestroyElement: function() {
$(window).off('scroll');
}
I am now looking to move the loading property to the controller so that I get a nice loader for the user.