React Native - Saving search terms after delay - react-native

I am creating a search toolbar that allows the user to see their most recent searches, using Realm Browser as my database. I save a search whenever the user types in the TextInput component, however, I don't want to add a search term after each key stroke, but only after the user has stopped typing for certain amount of time.
handleOnChange function will update state and only call getResults after 2 seconds
handleOnChange(text) {
this.setState({
searchStr: text
}, () => setTimeout(() => {
this.getResults()
}, 2000))
}
In getResults, I call my addRecentSearch function if certain criteria is met.
getResults() {
let searchTags = []
let searchCalcs = []
let tagNames = this.state.tags.map((tag) => {
return tag.name
})
if (this.state.searchStr.length >= 2 || this.state.tags.length !== 0) {
searchCalcs = Realm.searchCalcs(this.state.searchStr, tagNames)
Realm.addRecentSearch(this.state.searchStr)
}
this.setState({
results: searchCalcs,
tagsForFiltering: searchTags
})
}
So I use setTimeout to allow enough time for my state to get updated when the user types. Then, once the states been updated, I will want to add the search query. However, I'm not getting the results I expected when grabbing the most recent searches.
For example:
Type: "h"
Result: nothing happens as str must be at least 2 characters in length
Type: "he"
Result: meets criteria, and will add "he" as a recent search term.
Arr: ["he"]
Type: "heart" (Note: adding 3 characters in succession)
Result: It seems that even with the timeout function, my getResults function is being called (thus adding the search query for each character I added)
Arr: ["he", "heart", "heart", "heart"]
I want my arr to look like:
arr: ["he", "heart"]

You aren't fully debouncing in your example. You are only delaying everything by 2000ms. You need to create a timer and then reset it every time a change happens (by clearing and starting timer again). In this way, only the final 'delay' takes effect. Make sense?
You are very close to have written your own debounce function, so you can use clearTimeout, or there are some libraries that do it. See Lodash https://lodash.com/docs/#debounce

Related

Space character doesn't get recognized in lodash's debounce method with b-taginput in buefy?

I am using buefy's b-taginput with lodash's debounce method to fetch data from an api source during the #typing event. The issue is when I hit spacebar in the input field , inside the debounce method the input character is not recognized as an actual character.
<b-field label="Roles">
<b-taginput
:value="this.objectData.roles"
:data="filteredTags"
autocomplete
field="role"
icon="label"
placeholder="add role..."
#focus="getAsyncRole"
#typing="getAsyncRole"
#input="(newValue) => {updateValue(newValue, 'roles')}"
>
<template slot-scope="props">
<p>{{props.option.role}}</p>
</template>
<template slot="empty">There are no items</template>
</b-taginput>
</b-field>
getAsyncRole: debounce(function(name) {
console.log('inside getAsyncRole and name.length is '+name.length) // the length is 0 when i hit
spacebar but why?
if (!name.length) {
this.filteredTags = [];
return; //exits the function if length of input is zero
}
this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/role/?filter={role} LIKE '%25${name}%25'`)
.then(response => {
console.log('response for getasync role is'+JSON.stringify(response))
this.filteredTags = [];
response.forEach(item => {
this.filteredTags.push(item);
});
})
.catch(error => {
this.filteredTags = [];
throw error;
})
.finally(() => {
this.isFetching = false;
});
}, 500),
The above mentioned code works if I type any alphabetic character (i.e. it give's me the possible autocomplete results based on input character). But I also want it to list out all the autocomplete results (total results) when I hit spacebar into the b-taginput. Since it doesn't recognize the space character as an actual character, name.length become zero, and then it exits the function without making the api call.
NOTE: I noticed that this issue occurs only for b-taginput. This issue does not occur in the case of <b-autocomplete>. With <b-autocomplete> if I hit spacebar then I get all the results as desired. Therefore, I think this issue is specific only to b-taginput. Please help by advising a workaround for this.
The source code indicates that #typing trims the input before emitting it. This leaves a couple options, the best one (by far) is to pre-fetch the unfiltered list. With the list in hand, you can filter exactly as the example code does, searching for the input string within the list.
(The example code works because the empty string '' emitted by typing a space is "found" in every string)
Think about this: you're debouncing the API because you're concerned about hitting it too hard. Drop the debounce and just hit it once. Worried that fetching all tags is too long to wait? Just wait once and never wait again (consider that you were willing to incur this wait on every blank input).

Update data when value changes Vue Js

I have quite a complicated situation and i'm not amazing at Vue so I need some help.
I have a Firebase DB that gets an array (clients) and displays it.
const clientsRef = db.ref('clients')
firebase: {
clients: {
source: clientsRef,
//data has been retrieved from firebase
readyCallback: function() {
this.getSiteCount() // Get number of sites associated with client
this.loaded = true // Hide loader bar once this becomes true
}
}
},
On load complete getSiteCount() will get the clients unique ID and compare it against the sites DB and count how many exist. Below code simply loops around each client and then checks how many sites have the client_id of aClient['.key']. Not really important this works and gets the count and adds it to the clients array.
getSiteCount: function() {
this.clients.forEach((server, clientIndex) => {
this.clients[clientIndex].siteCount= 0
serverContactsRef.orderByChild('client_id').equalTo(server['.key']).on('child_added', (clientDetails) => {
this.clients[clientIndex].siteCount= this.clients[clientIndex].siteCount+ 1
})
})
},
Now in my html I have v-for="clients in filterClients" and the computed function...
filterClients: function() {
function compare(a, b) {
if (a.siteCount < b.siteCount) {
return 1
}
if (a.siteCount > b.siteCount) {
return -1
}
return 0
}
return this.clients.sort(compare)
}
I suspect because the getSiteCount() function runs once the clients have been pulled (0.5s delay) from the DB it's initial siteCount value is 0 and filterClients runs before those values get set. I need to delay filterClients() until the getSiteCount() function runs or need it to update automatically when the getSiteCount() function runs.
Can someone help me make sure the initial load of the page displays the clients in order of how many sites it has (siteCount)
It was in fact a Caveat.
Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
I changed
this.clients[clientIndex].siteCount= 0
to
Vue.set(this.clients[clientIndex], 'contractsNr', 0)
Updates when the data comes in perfectly now.
Thanks Jacob

Input field not reacting to data changes after being written to by a user

While creating a Vue.js application I have become stuck at a weird problem. I want to be able to manipulate an input field (think increment and decrement buttons and erasing a zero value on focus, so the user doesn't have to) and up until a user writes to the input field, everything is fine. After that, however, further changes in the data are no longer represented in the input field.
As I was sure I could not be the only one with this particular problem, I searched extensively, but had no luck. What baffles me the most is that everything works until the field is written to, since I can not really imagine why this would remove the data binding.
The following code should show the same behavior. It is an input field component, which is initialized with a zero value. On focus the zero gets removed. This works, until a user manually writes to the field after which zero values will no longer be removed, even though the focus method fires, the if-condition is met and the data in the amount-variable is changed.
Vue.component('item', {
data: function () {
return {
amount: 0
}
},
render: function (createElement) {
var self = this;
return createElement('input', {
attrs: {
//bind data to field
value: self.amount,
type: 'number'
},
on: {
//update data on input
input: function (event) {
self.amount = event.target.value;
},
//remove a zero value on focus for user convenience
focus: function (event) {
if (self.amount == 0 || self.amount == "0") {
self.amount = '';
}
}
}
})
}
})
I think you need to use domProps instead of attrs to make it reactive. But I would suggest you use vue's template syntax or if you insist on using the render function I would also suggest you to use JSX.

Resetting to initial data in Vue

I've got some form data that I display using a readonly input that is styled to look like plain text. When users click an edit button, they can then edit the inputs and either save or cancel.
My issue is obviously that when a user clicks cancel, the data they entered into the input remains (even though it isn't saved to the DB). I'm trying to figure out a way to reset the input to its initial data. I'm aware of this answer, but it doesn't seem to work because the data is fetched on creation.
This fiddle is similar except for the fact that the data in the real app comes from an axios call. The equivalent call is essentially:
fetch() {
axios.get(this.endpoint)
.then(({data}) => {
this.name = data.data;
});
}
Annoyingly, the fiddle actually works. However in my actual implementation it doesn't. The only difference with the app is that the data is an array.
How can I make this work?
This fiddle represents what my code actually does.
In the code:
data: () => ({
endpoint: 'https://reqres.in/api/users',
users: [],
initialData: []
}),
//...
edit: function(index) {
this.users[index].disabled = false
this.initialData = this.users
},
reset: function(index) {
this.users[index].disabled = true
this.users = this.initialData
}
Since users and initialData are arrays, you must use index when you access them.
So, at first sight, the change would be from:
this.initialData = this.users
To
this.initialData[index] = this.users[index]
But this won't work. Since this.users[index] is an object, whenever you change it, it will change what this.initialData[index] holds, since they are both just pointing to the same object. Another problem is that when you set it like that, the initialData won't be reactive, so you must use Vue.set().
Another thing, since you just want to reset the first_name property (the one you use at <input v-model="user.first_name" >), you should then assign user[].first_name to initialData[index].
Considering those changes to edit(), in the reset() method, the addition of [index] and of the .first_name field are enough. Final code:
edit: function(index) {
this.users[index].disabled = false
Vue.set(this.initialData, index, this.users[index].first_name);
},
reset: function(index) {
this.users[index].disabled = true
this.users[index].first_name = this.initialData[index]
}
JSFiddle: https://jsfiddle.net/acdcjunior/z60etaqf/28/
Note: If you want to back up the whole user (not just first_name) you will have to clone it. An change the order of the disabled property:
edit: function(index) {
Vue.set(this.initialData, index, {...this.users[index]});
this.users[index].disabled = false
},
reset: function(index) {
Vue.set(this.users, index, this.initialData[index]);
}
JSFiddle here. In the example above the clone is created using the spread syntax.
Input is immediately updating the model. If you want to do something like edit and save you have to take a copy and edit that. I use lodash clone to copy objects then update the fields back when save is clicked. (of course sending message to server.)

Getting id from row clicked on a dgrid List

I just started using dgrid, and going through the dTunes sample, I'm unable to find the id associated with each row in the list. This is pretty remedial on my part, but how would I also get the id I sent from the datasource?
define([
'require',
'dgrid/List',
'dgrid/OnDemandGrid',
'dgrid/Selection',
'dgrid/Keyboard',
'dgrid/extensions/ColumnHider',
'dojo/_base/declare',
'dojo/_base/array',
'dojo/Stateful',
'dojo/when',
'dstore/RequestMemory',
'put-selector/put',
'dojo/domReady!'
], function (require, List, Grid, Selection,
Keyboard, Hider, declare, arrayUtil, Stateful,
when, RequestMemory, put) {
var cstsNode = put(listNode, 'div#cstsCars');
...
var cstsList = new TunesList({}, cstsNode);
var dataCSTS = new RequestMemory({ target: require.toUrl('./dataCSTS.json') });
...
dataCSTS.fetch().then(function (cars) {
cstsCars = arrayUtil.map(cars, pickField('Description'));
cstsCars.unshift('All (' + cstsCars.length + ' CSTS Cars' + (cstsCars.length !== 1 ? 's' : '') + ')');
cstsList.renderArray(cstsCars);
});
...
cstsList.on('dgrid-select', function (event) {
var row = event.rows[0];
console.log(row.id); // shows row number. How do I get the real id or other fields?
console.log(row.data); // shows row text that is displayed ("sample text 1")
console.log(row.data.id); // undefined
});
Here is a snippet of sample data like I'm supplying:
[{"id":"221","Description":"sample text 1"},
{"id":"222","Description":"sample text 2"},
{"id":"223","Description":"sample text 3"}]
I'd like to see the id. Instead, row.id returns 1,2 and 3, ie the row numbers (or id dgrid created?).
You haven't really shown a complete example, but given that you're using a store anyway, you'd have a much easier time if you let dgrid manage querying the store for you. If you use dgrid/OnDemandList (or dgrid/List plus dgrid/extensions/Pagination), you can pass your dataCSTS store to the collection property, it will render it all for you, and it will properly pick up your IDs (since Memory, and RequestMemory by extension, default to using id as their identity property).
The most appropriate place to do what you're currently doing prior to renderArray would probably be in the renderRow method if you're just using List, not Grid. (The default in List just returns a div with a text node containing whatever is passed to it; you'll be passing an object, so you'd want to dig out whatever property you actually want to display, first.)
If you want a header row, consider setting showHeader: true and implementing renderHeader. (This is false in List by default, but Grid sets it to true and implements it.)
You might want to check out the Grids and Stores tutorial.
I think the problem might be that I was modeling my code based on the dTunes sample code, which has 3 lists that behave a little differently than a regular grid.
For now, I'm using the cachingStore that is available in the lists. So the way I get the id:
cstsList.on('dgrid-select', function (event) {
var row = event.rows[0];
var id = storeCSTS.cachingStore.data[row.id - 1].id; // -1 because a header was added
console.log(id);
});
I'm not sure whether this will work if I ever try to do sorting.