Manage multiple state array searches and keep the original state array? - reactjs

I'm trying to build a search function to be able to search an Array in state.
var searchString = "search word"
var updatedList = cases.filter((item) => {
return Object.keys(item).some(
key => (item as any)[key]
.toString()
.toLowerCase()
.search(searchString.toLowerCase()) !== -1
);
});
this.setState({
cases: updatedList
})
This updates state.cases and renders the items found in the Array with objects, which is good. But when I'm doing another search, state.cases is of course updated and contains only the items from the search before.
How can I keep the "old" state before the first search and make multiple searches?
The cases in state is used in a component that renders a table with the result.

Maybe don't set the state for the filtered cases ..? Just set the state for the search string and try this
render() {
const { searchString } = this.state;
const { cases } = this.props;
let filteredCases = cases;
if(!!searchString) {
filteredCases = cases.filter((item) => {
return Object.keys(item).some(
key => (item as any)[key]
.toString()
.toLowerCase()
.search(searchString.toLowerCase()) !== -1
);
});
}
return <>{filteredCases}</>
}
So you will just filter out the cases based on your search string.

Related

Only rerender and fetch when api property has changed

Im trying to get the use effect to rerender only when the labels property has changed.
i want to fetch newest changes in the labels property only when there is a change.
My code below:
const usePrevious = (value: any) => {
const ref = useRef()
useEffect(() => {
ref.current = value
}, value)
return ref.current
}
const prevLabels = usePrevious(labels)
useEffect(() => {
if (labels !== prevLabels) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
const fetchUpdatedLabels = async () => {
await axios
.get(`/events/${printEvent.id}`)
.then((response) => {
const newLabels = response.data.labels
// filter response to empty array if there are no splits left
// if is null return empty string
let fetchedLabels =
newLabels !== null
? newLabels
.toString()
.split(',')
.filter((label: string) => {
return label !== ''
}) || ''
: ''
getLabels(fetchedLabels)
print.updateEvent(printEvent.id, 'value', printEvent.value)
})
.catch((err) => {
console.error(err, 'cant fetch labels from api')
})
}
it keeps refetching nonstop, how do i achieve this?
This is most likely in part because you are comparing an array with an array using the equivalence operator !==. It can be surprising to people new to JS but if you do this in a JS console:
[1,2,3] === [1,2,3]
It returns false. The reason is that an array is an object and what you are asking is "is the array on the left literally the same as the one on the right" -- as in the same variable. Whereas these are 2 separate array instances that happen to have the same contents. You might wonder why it works then on strings and numbers etc, but that's because those are primitive values and not objects which are instantiated. It's a weird JS gotcha.
There are several ways to compare an array. Try this:
useEffect(() => {
if (labels.sort().join(',') !== prevLabels.sort().join(',')) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
I'm not sure about your use case here though as when the labels are different, you fetch them, but the only way they can change is to be fetched? Is there something else in the code that changes the labels? If so, won't they get wiped out by the ones from the server now as soon as they change?
Something else is wrong here I think. Where do you set labels into state?

List to changes and return the ids of the objects that had changes in React

So I have two states in React, one is a copy that doesn't change, and the other one changes, so with a use effect I'm listening for changes to the array that changes and call a function, which the main function is iterate the arrays and check two specific values if they did change in case they are not same while compared to the old state, return the id of that object and put into an array, and at the end return it.
So:
const [oldData, setOldData] = useState(...old data here)
const [newData, setNewData] = useState(...new data here)
useEffect(() => {const ids = Compare(oldData, newData)} => [newData])
const Compare = (oldData, newData) => {
for (let keys in oldData) {
if (newData[keys] && oldData[keys] !== newData[keys]) {
return true;
}
}
}

mistakes in filter in React state

I have got an initial state in reducer. I get from api products and I should check by key, if product was not repeated, I put it to the state. If was repeated - do nothing. Mistake is that filter does not work and products repeat.
const defaultState = {
productsInMyList: [],
};
//get prodouct
const { productsInMyList } = action.payload;
//check, if it is in state or not
const sameProductsInMyList =
!!state.productsInMyList.includes(
productsInMyList.key
);
const newProductsInMyList = sameProductsInMyList
? state.productsInMyList
: [...state.productsInMyList, productsInMyList];
return {
...state,
productsInMyList: newProductsInMyList,
};
I suspect, based on a comment of yours, that state.productsInMyList is an array of objects and that productsInMyList is also an object. Array.prototype.includes only works with primitives and shallow object equality (i.e. reference equality).
If you are comparing objects in an array and want to know if an array contains some element matching a criteria then you will want to use Array.prototype.some, i.e. does there exist some element in this array meeting this condition.
const sameProductsInMyList = state.productsInMyList.some(
product => product.key === productsInMyList.key
);
I changed includes into find. Thanks all for the help.
const sameProductInMyList = !!state.productsInMyList.find(
(item) => item.key === productsInMyList.key
);

What is proper way to call function from onClick so it don't trigger wrong one?

When I click on button then onClick triggers correct function, run half through and jumps to other function which is not related to it and run through half of it and jumps back to first function, runs half trough again and drops error
Uncaught TypeError: _this.state.searchValue.toLowerCase is not a function
Interesting part is that I click other button before which triggers this function with toLowerCase() and there is no errors.
I dont have any idea whats going on here but so far i was trying to remove few lines to see which line cause it because I dont think that line with toLowerCase() realy is the reason. Everything works when I remove lines where is first this.setState.
Here is my function:
( Alerts is used to track where function is at, that how i know
that it run half through only. It never reach alert("DDD").
This function is which is triggered with button onClick like it should be )
onSelect = (e) => {
const data = e.target.getAttribute('data-id');
const itemId = e.target.getAttribute('data-id');
const itemIdState = !this.state[e.target.getAttribute('data-id')];
alert("AAA")
this.setState(state => { // <--- Somehow problem comes from this setState function
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
alert("CCC")
this.setState(State => ({
[itemId]: itemIdState,
}), function() {
alert("DDD")
if(this.state[itemId] === true){
this.setState({isAnySelected: true})
}else if(this.state[itemId] === false){
this.setState({isAnySelected: false})
}
})
}
This is other function which is triggered by mistake and is not related to other. It is just returning component which is displayed and when I press on its button then i have this issue.
filterSearch = (id, title, path) => {
let name = title.toLowerCase()
let filter = this.state.searchValue.toLowerCase()
if(name.includes(filter)){
return <SearchResult key={id} data-id={id} pName={path} onClick={this.onSelect} selected={this.state[id]} />
}
}
And here is from where filterSearch is triggered. Behind this.props.searchResult is Redux.
{this.props.searchResult ? this.props.searchResult.map(category =>
this.filterSearch(category.id, category.title, category.path)
) : null
}
I think I see what the problem is: in your problematic this.setState, you cast everything in your state to a boolean:
this.setState(state => {
const newState = {};
for (const dataId in state) {
newState[dataId] = dataId === data
}
alert("BBB")
return newState
});
Your for() statement ends up comparing searchValue to data (some kind of ID), which I imagine more often than not will not be the case, so searchValue ends up getting set to false.
And what happens when you try to do .toLowerCase() on a Boolean?
To fix this, consider structuring your state like this:
this.state = {
searchValue: '',
ids: {},
};
Then, replace your problematic this.setState with something like this:
this.setState((state) => {
const newIDs = {
// Create a clone of your current IDs
...state.ids,
};
Object.keys(newIDs).forEach(key => {
newIDs[key] = key === data
});
alert("BBB")
return {
// searchValue will remain untouched
...state,
// Only update your IDs
ids: newIDs,
}
});
What exactly are you wanting to do here?
this.setState(state => {
const newState = {}; // You are initializing an object
for (const dataId in state) {
newState[dataId] = dataId === data // You are putting in an array every property of state that is equal to data
}
return newState
});
So irrevocably, your this.state.searchValue property will be changed to something else, which is of boolean type. So toLowerCase being a function for string.prototype, you will get an error.
You should describe what you where aiming to get here.

How to get filtered object length

I am trying to show that there is no data to display when the size of filtered object is zero. Is there any way to check its size after calling filter method?
this.state.items
.filter(item => {
// filter code
})
.map(item => {
// display data
})
For example, this.state.items has ten items and becomes zero after passing through filter method. I'd like to display the message at this point.
First store the filtered data in a variable:
let filteredData = this.state.items
.filter(item => {
// filter code
});
Now filteredData will contain the filtered data, now you can check the length of this variable and return 'No Data Found' if length = 0, Use either ternary operator:
return filteredData.length ?
filteredData.map(item => {
// display data
})
:
<div>No data found </div>;
Or you can use if-else condition also:
if(filteredData.length)
filteredData.map(item => {
// display data
})
else
<div>No data found </div>
const filteredData = this.state.items((items) => //filter code);
return filteredData.count()
? filteredData.map(item => //data);
: <div>No Data found </div>
Try this one with immutable object.

Resources