Sectioning data in a ListView - react-native

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.

Related

How to create a Crossword board in React Native

I'm trying to create a crossword game in react native. I'm having trouble starting off with the gameboard. I think I'm going to have the crosswords stored in an object like
{
across: {
1: {
question: "test",
answer: "test",
position:(0,0),
length: 4,
}
}
down:{}
}
Would it make sense to create a matrix of 0 for black squares 1 for white squares and 2 for word starting squares. Then use a flat list to build out the matrix visually?
Any help or advise on another way to do it would be appreciated.
Cheers,
I've. tried. using flat lists but the indexing becomes very complicated and I'm hoping there is a better way.
I made one of those React Pathfinding visualizers and basically just had an array I kept track of thru state for if it was filled or not. Map/ForEach that grid and plop down what you would have as another component shall we say Node passing whatever information is needed as props.
This example may not be the best, due to it being React and not React Native (small difference really)... and there is a lot to this that doesn't apply to your scenario but I think it shows what I mentioned in the beginning.
<div className="grid">
{grid.map((row, rowId) => {
return (
<div key={rowId}>
{row.map((node, nodeId) => {
const { row, col, isFinish, isStart, isWall } = node;
return (
<Node
key={nodeId}
row={row}
col={col}
isStart={isStart}
isFinish={isFinish}
isWall={isWall}
mouseIsPressed={this.state.mouseIsPressed}
onMouseDown={(row, col) => this.handleMouseDown(row, col)}
onMouseEnter={(row, col) =>
this.handleMouseEnter(row, col)
}
onMouseUp={() => this.handleMouseUp()}
></Node>
);
})}
</div>
);
})}
</div>
I'd map the crossword data to an array of fields. There are three types of fields in crosswords: question block, fillable block and dead block.
Algorithmically, there are countless options. One would be to first convert every question to its blocks and then convert all of these to a flat array of blocks, combined.
Extra tip: consider using an array of questions instead of an object indexed by numbers. These indexes don't matter anyway.

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

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.

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
}

For each item in a Vuex Store, assign a score (based on a calculation), then sort all by score

I'm trying to get my head around how to best implement the following requirements into a Vuex (vue.js) application.
I'll try and keep it as simple as possible, what i'm looking to achieve is something similar to Hacker News site. Therefore application will need to calculate a score for each item (post) based on a calculation/algorithm. The score is determined by a combination of likes and the date it was created on. Therefore the scores are constantly updating (until a designated time period expires) therefore it the score is not stored in a data source but calculated by the app.
So my main question is, how best to go about implementing this for a Vuex application?
I have set up a Vuex store of posts (i've called each article a 'post') and I'm able to display all the posts in a list. So now i've have two main objectives:
Assign a score to the posts (articles)
Filter the posts results list by their scores.
I've done some research and the consensus feels like it would be best to keep the 'score' separate from the 'posts' store (not added to the 'posts' array).
The following code works fine, however feels verbose. Would it be better to move any of these functions to 'getters/mutations/actions'?
Please note: for this examples i've simplified the calculation/algorithm to 'likes' total plus 'comments' total, obviously the final algorithm is more complicated to work like hacker news.
<div>
<button #click="postFilterKey = 'all'"><button>
<button #click="postFilterKey = 'orderByScore'"><button>
</div>
<div v-for="post in filterPosts" v-bind:key="post.value">
<p>Title: {{ post.title }}</p>
<p>Total Likes: {{ post.likes }}</p>
<p>Total Comments: {{ post.comments }}</p>
<p>Score: {{ post.comments | getScore(post.likes) }}</p>
</div>
computed: {
...mapState(["posts"]),
filterPosts () {
return this[this.postFilterKey]
},
all () {
return this.posts;
},
orderByScore (){
return this.posts.sort((b,a) =>
this.calculateScore(a.totalComments, a.totalLike) -
this.calculateScore(b.totalComments, b.totalLike));
},
methods: {
calculateScore(totalComments, totalLikes) {
let score = totalComments + totalLikes
return score;
},
}
filters: {
getScore(totalComments, totalLikes) {
let score = totalComments + totalLikes
return score;
},
}
Thanks for having a look.
Like you said, what you have will work fine, and there is nothing wrong with it, per se.
There are a couple issues with your current code that could be improved:
If you want to reuse the score anywhere else in your app that is not a direct child of this component, you will have to duplicate the getScore() function in that component which is harder to maintain.
Every time the page is rendered the getScore() function gets re-run once for every post in the list, and every time the user sorts the list, the calculateScore() and getScore() functions will re-run once for each item in the list, which is not very efficient.
One solution, as you alluded to, is to put these functions in the store as getters. This would solve both issues I listed above by:
Keeping your code DRY by keeping these functions in one location.
Running these functions as vuex getters will cache the results so that they only re-run if the state they are dependent on changes.
An example of what your getter property in your store may look like is:
getters: {
postsWithScore: state => {
return state.posts.map(post => {
return {
...post,
score: post.comments + post.likes,
});
}
}
See the vuex docs for more info: https://vuex.vuejs.org/guide/getters.html
I would consider moving the score calculation to a getter. It feels like a piece of the business model, why should it be fixed in a component that's merely using it? From what I recall, vuex's getter have some caching built in, so there's performance gain as well.
PS. Dynamic scoring base on time has its downsides btw. You probably don't wanna move the items around while user looks at them, but what about pagination? When user goes to the next page and your score changes in the meantime, then the continuity of the list is broke. Anyhow, it's just kinda UX concern I believe.

Dynamic Tabs/Lists with Ember js

I'm trying to learn Ember js, doing some experiments, so far not much success, but slowly moving forward.
But now I got stuck, I'm trying to create a dynamic tabs without router. I have these two fiddles
http://jsfiddle.net/drulia/BzRUF/
http://jsfiddle.net/drulia/uNNXy/
one simple, keeping references in the controller and another one with ContainerView, but I have stuck on both approaches. I tried StateManager as well, but once again with no luck.
Problem in first fiddle is that I found no other way to get element's content in the View than using this._parentView.get('content'); which is not right because I'm not suppose to use anything with prefix _ . But I have no idea how else I can actually check if element belongs to active tab.
Second fiddle main problem is that I have no clue how can I attach content to the tabs. Also struggling with ability to remove tabs, because {{action remove this target="App.Tabs"}} allways points to the same element.
I been reading all guides and API on http://emberjs.com, also was reading plenty of other tutorials, most of them have no real value because they outdated, especially for me newbie, because it is already hard enough to attach together up to date pieces provided in the official page.
This todo app example though, was very useful https://github.com/trek/ember-todos-with-build-tools-tests-and-other-modern-conveniences It is very good quality, but areas like tabs is handwritten and they work via router.
To sum-up, at the moment the Views is quite a mystery for me, so any light helping out with dynamic tabs would be much appreciated.
Solution
http://jsfiddle.net/drulia/BzRUF/9/
Not perfect, but does the job, you can navigate, create and delete the tabs.
To make it really usable, there should be some id's with tabs, so then tabs could have same title. But the idea is there and I truly hope that someone will find it useful.
Below are main part from of js to get the idea what's going on
App.IndexController = Ember.ArrayController.extend({
tabs: ['Tab1','Tab2'],
activeTab: 'Tab1',
counter: 2,
closeTab: function(tab) {
var i = this.tabs.indexOf(tab);
this.tabs.removeAt(i);
if(tab === this.activeTab)
this.set('activeTab',this.tabs.objectAt(0));
},
createTab: function() {
var newTab = 'Tab' + ++this.counter;
this.tabs.pushObject(newTab);
this.set('activeTab',newTab);
}
});
App.TabInputView = Ember.TextArea.extend({
placeholder: function() {
return 'Empty Area of ' + this.tab;
}.property(),
isVisible: function(s) {
var activeTab = this.get('controller.activeTab');
return Boolean(activeTab === this.tab);
}.property('controller.activeTab')
});
And here the main part of html
{{#each tab in tabs}}
{{#view App.TabView tabBinding="tab"}}
{{tab}} <span class="close" {{action closeTab tab bubbles=false}}>x</span>
{{/view}}
{{/each}}
<button {{action createTab}}>+</button>