I'm a beginner to React; I understand that setState is asynchronous, but I don't understand why in the example pen below the box below the refresh is not done immediately, and only updates after the second character is input.
Codepen: (updated to link to correct pen)
https://codepen.io/anon/pen/odZrjm?editors=0010
Portion:
// Returns only names matching user's input
filterList = (term) => {
let finalList = []
for (let x = 0; x < this.state.names.length; x++) {
if (this.state.names[x].toLowerCase().includes(term)) {
finalList.push(this.state.names[x])
}
}
finalList.sort()
return finalList
}
// Returns the name list as string
listNames = () => {
let filteredNames = [], filter = this.state.filter.toLowerCase(), names = ""
if (filter === "") return "Enter filter chars"
// Generate filtered array with only names matching filter
filteredNames = this.filterList(filter)
// Build and return comma-separated list of filtered names
names = filteredNames.join(', ')
if (names.length === 0) return 'None found'
return names
}
handleChange = (event) => {
let result, alert = 'alert-primary'
result = this.listNames()
this.setState({
filter: event.target.value,
namesList: result
})
}
If I replace the line "result = this.listNames()" in handleChange with just "result = 'test'" then it updates immediately. Can you please explain why it does not when I call the functions?
It occurs because you are calling the listNames method before this.state.filter is set, thus reaching:
if (filter === "") return "Enter filter chars"
Here's a working pen: https://codepen.io/anon/pen/gzmVaR?editors=0010
This is because listNames() is being called before setState
when you call handleChange method, it can't find 'this' scope.
so 'this' is undefined and can't call this.listNames().
solution:
use onChange={this.handleChange.bind(this)}
instead of onChange={this.handleChange}
Related
I have data in values from Formik library which is mapped, and each object of values(Formik) gets some data assigned to two properties.
When I console.log('valuesDeepcopy', valuesDeepcopy) it shows that data is properly assigned to pointed properties to the copy of each element, but when I console.log('values',values) I see that it's not updating Formik's value.
values.luxmeds.map((person) => {
const filter = luxmedTypesPackages.filter((type) => {
if (type.package === person.luxmedType.package && type.type === person.luxmedType.type)
return true;
return false;
});
if (!filter.length) return person;
const valuesDeepcopy = JSON.parse(JSON.stringify(person));
valuesDeepcopy.luxmedTypeId = filter[0]?.id;
valuesDeepcopy.luxmedType.id = filter[0]?.id;
console.log('valuesDeepcopy', valuesDeepcopy);
console.log('filter', filter);
return valuesDeepcopy;
});
console.log('values', values);
Can You please suggest what should be done make Formik's value update ?
thanks
This gets called on the onkeyup event in my html search bar input.
I walked through it with the debugger and it is actually getting the searchInput that the user types, but something about the filter() function doesn't work. It doesn't actually filter through the storageSvc array and then re-rendering the table with the filtered data. "name" is part of an object in my data array.
No errors, its just not actually filtering the data.
function searchTeam(){
let searchInput = document.getElementById("searchInput").value;
storageSvc.filter({name:searchInput});
// re render table
renderTable("#tableContainer", storageSvc.list());
}
filter(filterObj) {
//returns a copy of the filtered array
///e.g., storageSvc.filter({coachLicenseLevel:1});
return this.model.data.filter((d) => {
for (const key of Object.keys(filterObj)) {
if (d[key] !== filterObj[key]) {
return false;
}
}
return true;
});
}
You're not actually using the returned value from the filter function.
Try assigning the returned value of the filter function to a variable and then pass that variable to your render fucntion:
function searchTeam() {
let searchInput = document.getElementById("searchInput").value;
const filteredResults = storageSvc.filter({ name: searchInput });
// renderTable("#tableContainer", storageSvc.list() );
renderTable("#tableContainer", filteredResults);
}
function filter(filterObj) {
//returns a copy of the filtered array
///e.g., storageSvc.filter({coachLicenseLevel:1});
return this.model.data.filter((d) => {
for (const key of Object.keys(filterObj)) {
if (d[key] !== filterObj[key]) {
return false;
}
}
return true;
});
}
I have this:
validateForm = () => {
for (let i = 0; i < formInputs.length; i++) {
const inputName = formInputs[i];
if (!this.state.form[inputName].length) {
return false;
}
}
}
which im refactoring in to this:
validateForm2 = () => {
Object.keys(this.state.form).map(input => {
if(!this.state.form[input].length) {
return false
}
return true;
})
}
the first one works, when i fill in my form and the function returns true, if one is empty it returns false.
however i cant seem to quite understand the return keyword to get the same result.
Object.keys says it returns an array but even if I say return Object.keys... or else {return true} I don't seem to get the same result. what am I misunderstanding about return?
You could use Array#every, which uses the return value for a short circuit and for returning the check of all truthy items.
validateForm2 = () =>
Object.keys(this.state.form).every(input => this.state.form[input].length);
Array#map utilizes the return value as new item for each item of the array for a new array, which is dicarded in the given example.
In the first example you have only one (arrow) function which returns either false or undefined.
In the second example you have outer (arrow) function that never returns anything - undefined to the calling code, and the second function that you pass as a parameter to Array.map method. return statements inside the parameter function are not returning anything from the outer function.
validateForm2 = () => {
var emptyItems = Object.keys(this.state.form).filter(input => {
return !this.state.form[input].length;
});
return emptyItems.length == 0;
}
You could modify your function to do what you want it to do.
validateForm2 = () => {
return Object.keys(this.state.form).every(input => {
return this.state.form[input].length;
})
}
You are checking that every property has a length (true). If one of them doesn't, your function returns false.
I think you can avoid using .map in favor of .every() which iterates over every single element and checks whether it has a length greater than zero.
const validateForm = (form) => Object.values(form).every((field) => field.length);
let semiEmptyForm = {
firstField : "",
secondfield : "notEmpty"
};
let nonEmptyForm = {
firstField : "notEmpty",
secondfield : "notEmpty"
};
console.log(validateForm(semiEmptyForm))
console.log(validateForm(nonEmptyForm))
It's driving me crazy. I've created a list with several entries. I added a filtering function, which seems to work fine. I've checked the number of results returned, but somehow it just showing the result number beginning at the first row.
For explanation:
Let's assume I search for "Zonen" and my filter function returns 4 rows with ID 23, 25, 59 and 60, the rows with ID's 1,2,3 and 4 are displayed. What I'm doing wrong!?
...
render() {
let filteredList = this.state.freights.filter((freight) => {
let search = this.state.search.toLowerCase();
var values = Object.keys(freight).map(function(itm) { return freight[itm]; });
var flag = false;
values.forEach((val) => {
if(val != undefined && typeof val === 'object') {
var objval = Object.keys(val).map(function(objitm) { return val[objitm]; });
objval.forEach((objvalue) => {
if(objvalue != undefined && objvalue.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
});
}
else {
if(val != undefined && val.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
}
});
if(flag)
return freight;
});
...
<tbody>
{
filteredList.map((freight)=> {
return (
<Freight freight={freight} onClick={this.handleFreightClick.bind(this)} key={freight.id} />
);
})
}
</tbody>
...
UPDATE
freights is loaded and filled via AJAX JSON result. One object of freights looks like this:
I have a textbox where a user can perform a search. This search should return all freight objects which properties contain the search string.
The filter is so complex, because I want to also to search in sub-objects of freight. Maybe there is a more simple way?
"Zones" was just an example for a search string the user can search for.
Now that your intentions are clearer, I suggest this much less complex solution.
First, you can write a recursive utility fn to get all values of all keys in an n-depth object. Like this, for example (I'm using lodash's utility fn isObject there):
const getAllValues = (obj) => {
return Object.keys(obj).reduce(function(a, b) {
const keyValue = obj[b];
if (_.isObject(keyValue)){
return a.concat(getAllValues(keyValue));
} else {
return a.concat(keyValue);
}
}, []);
}
Now that you have an array of all object's values, it makes your filter very simple:
let filteredList = this.state.freights.filter((freightItem) => {
const allItemValues = getAllValues(freightItem);
return allItemValues.includes(this.state.search);
});
That should be it. If something is not working, gimme a shout.
I have found the solution why the "wrong" freight entries are displayed.
I needed to add in freight component the componentWillReceiveProps method:
componentWillReceiveProps(nextProps) {
if(nextProps.freight) {
this.setState({
freight: nextProps.freight
});
}
}
Then everything worked fine.
Have filter, it's filtering ok, but when clear input. I see filtered result:
filterList(event) {
var updatedList = this.state.items;
if (event.target.value !== '') {
updatedList = updatedList.filter(function(item){
return item.name.toLowerCase().indexOf(event.target.value.toLowerCase()) !== -1;
});
}
this.setState({items: updatedList}); // now this.state.items has a value
}
My condition not working.
https://plnkr.co/edit/dPZM9BZVa4uzEMwdNpZ8?p=catalogue full component in script.js
There's a better approach for this. You usually don't want to use props to set your state directly. What you want is to only use the state for filtered item sets, since your props will always contain the full set of items. So first, remove your componentWillReceiveProps function. Then change the following things:
// in your constructor
this.state = {
filteredItems: false
}
filterList(e) {
var value = e.target.value;
this.setState({
filteredItems: !value
? false
: this.props.items.filter(function(item) {
return item.name.toLowerCase().indexOf(value.toLowerCase()) > -1
})
})
}
// inside render
var elems = this.state.filteredItems || this.props.items