my course is telling me this is the solution to checking if contacts already has {name} in it.
useEffect(() => {
const nameIsDuplicate = () => {
const found = contacts.find((contact) => contact.name === name);
if (found !== undefined) {
return true;
}
return false;
};
if (nameIsDuplicate()) {
setDuplicate(true);
} else {
setDuplicate(false);
}
}, [name, contacts, duplicate]);
Does my code do the same thing?
useEffect(() => {
if (contacts.includes(name)) {
setDuplicate(true)
} else {
setDuplicate(false)
}
}, [name, contacts, duplicate]);
No, contact seems to be a list, like:
[
{name: "Mario"},
{name: "Andrea"},
...
]
I guess this given the predicate for the find method:
contact.name === name
In the second example it is using the includes passing the same name variable that seems to indicate a string, so in the second snippet contacts.includes will be always false
Related
I have a little problem with filtering my array.
I want display a product filtered by input value with a name or platform or something other value. With name is no problem, but i don't know how to do it with platforms.
Bottom is my logic and file with products, txh very much for help
live: live
repo: repo
const [inputText, setInputText] = useState('')
const inputHandler = e => {
const text = e.target.value.toLowerCase()
setInputText(text)
}
const filteredData = PRODUCT_LIST.filter(el => {
if (inputText === '') {
return
} else {
return el.name.toLowerCase().includes(inputText)
}
})
const PRODUCT_LIST = [
{
id: 'gow',
name: 'God of War',
developer: 'Santa Monica Studio',
category: 'games',
platform: 'PlayStation 4',
version: 'PL',
price: 39,
},]
You need to make a conditional check.
First, check whether the search text matches the name; if it fits, return the list. If not, then check whether it matches the platform.
const filteredData = PRODUCT_LIST.filter(el => {
const findByName = el?.name?.toLowerCase().includes(inputText);
const findByPlatform = el?.platform?.toLowerCase().includes(inputText);
if (inputText === '') {
return
} else {
return findByName || findByPlatform
}
})
You should do something like this just return true when you are getting empty string. Let me know if it works.
const filteredData = PRODUCT_LIST.filter(el => {
if (inputText === '') {
return true;
} else {
return (el.name.toLowerCase().includes(inputText.toLowerCase()) || el.platform.toLowerCase().includes(inputText.toLowerCase()))
}
})
The goal is to make an AutocompleteInput check for the filter value not only in the suggestion list directly, but also in the suggestions' references to different resources.
Specifically, say a Quote has a reference to a Contact and to an Address, and the user enters 'abc' in the input. Now, a Quote whose address contains 'abc' should also be displayed in the suggestion list.
The most elegant way would be to use the useGetOne hook like in the following code snippet but you can't call that hook from outside a React component.
const matchAnyNested = (filter, value) => {
if (matchAnyField(filter, value)) return true;
const { data: contact } = useGetOne('contacts', value.contact_id);
if (matchAnyField(filter, contact)) return true;
const { data: account } = useGetOne('accounts', contact.account_id);
if (matchAnyField(filter, account)) return true;
for (let item of value.part_items) {
const part = useGetOne('parts', item.part_id);
if (matchAnyField(filter, part)) return true;
}
return false;
};
[...]
<AutocompleteInput ... matchSuggestion={matchAnyNested} />
Is there a way to fetch records from within the matchSuggestion function or some other way to validate suggestions based on nested records ? Thanks for any help
Because of the React rules of hooks, this doesn't seem to be possible. I ended up implementing this filtering functionality in the backend.
The useGetOne hook, just like other dataProvider hooks, accepts an enabled option. The example from the react-admin documentation shows its usage:
// fetch posts
const { ids, data: posts, loading: isLoading } = useGetList(
'posts',
{ page: 1, perPage: 20 },
{ field: 'name', order: 'ASC' },
{}
);
// then fetch categories for these posts
const { data: categories, loading: isLoadingCategories } = useGetMany(
'categories',
ids.map(id=> posts[id].category_id),
// run only if the first query returns non-empty result
{ enabled: ids.length > 0 }
);
It applies to your case:
const matchAnyNested = (filter, value) => {
const { data: contact } = useGetOne(
'contacts',
value.contact_id,
{ enabled: !matchAnyField(filter, value) }
);
const { data: account } = useGetOne(
'accounts',
contact.account_id,
{ enabled: !matchAnyField(filter, contact) }
);
// ...
};
This won't solve your problem in the loop, though, because of the rules of hooks.
If you do need that loop, your best bet is to use the useDataProvider hook to call the dataProvider directly:
const matchAnyNested = async (filter, value) => {
const dataProvider = useDataProvider();
if (matchAnyField(filter, value)) return true;
const { data: contact } = await dataProvider.getOne('contacts', { id: value.contact_id });
if (matchAnyField(filter, contact)) return true;
const { data: account } = await dataProvider.getOne('accounts', { id: contact.account_id });
if (matchAnyField(filter, account)) return true;
for (let item of value.part_items) {
const part = await dataProvider.getOne('parts', { id: item.part_id });
if (matchAnyField(filter, part)) return true;
}
return false;
};
I'd like to remove a nested object based on the id is equal to a passed prop. At the moment, the entire object is replaced. I'm missing something, when trying to update the state using useState probably with the way I'm looping my object?
UPDATE: The question was closed in response to available answers for updating nested objects. This question involves arrays which I believe are part of the issue at hand. Please note the difference in nature in this question with the forEach. Perhaps a return statement is required, or a different approach to the filtering on id..
my initial object looks like this:
{
"some_id1": [
{
"id": 93979,
// MORE STUFF
},
{
"id": 93978,
// MORE STUFF
}
],
"some_id2": [
{
"id": 93961,
// MORE STUFF
},
{
"id": 93960,
// MORE STUFF
}
]
}
and I go through each item as such:
for (const key in items) {
if (Object.hasOwnProperty.call(items, key)) {
const element = items[key];
element.forEach(x => {
if (x.id === singleItem.id) {
setItems(prevState => ({
...prevState,
[key]: {
...prevState[key],
[x.id]: undefined
}
}))
}
})
}
There are 3 problems in your code:
You are setting the value of key to an object while the items is expected to have an array to ids.
// current
[key]: {
...prevState[key],
[x.id]: undefined
}
// expected
[key]: prevState[key].filter(item => item.id === matchingId)
If you intend to remove an object from an array based on some condition, you should be using filter as pointed out in Owen's answer because what you are doing is something else:
const a = { xyz: 123, xyz: undefined };
console.log(a); // { xyz: undefined} - it did not remove the key
To make your code more readable, it is expected to manipulate the entire object items and then, set it to the state once using setItems - in contrast to calling setItems multiple times inside a loop and based on some condition.
This makes your code more predictable and leads to fewer re-renders.
Also, the solution to your problem:
// Define this somewhere
const INITIAL_STATE = {
"some_id1": [
{
"id": 93979
},
{
"id": 93978
}
],
"some_id2": [
{
"id": 93961
},
{
"id": 93960
}
]
};
// State initialization
const [items, setItems] = React.useState(INITIAL_STATE);
// Handler to remove the nested object with matching `id`
const removeByNestedId = (id, items) => {
const keys = Object.keys(items);
const updatedItems = keys.reduce((result, key) => {
const values = items[key];
// Since, we want to remove the object with matching id, we filter out the ones for which the id did not match. This way, the new values will not include the object with id as `id` argument passed.
result[key] = values.filter(nestedItem => nestedItem.id !== id)
return result;
}, {});
setItems(updatedItems);
}
// Usage
removeByNestedId(93961, items);
Probably a simple reduce function would work, Loop over the entries and return back an object
const data = {"some_id1": [{"id": 93979},{"id": 93978}],"some_id2": [{"id": 93961},{"id": 93960}]}
const remove = ({id, data}) => {
return Object.entries(data).reduce((prev, [nextKey, nextValue]) => {
return {...prev, [nextKey]: nextValue.filter(({id: itemId}) => id !== itemId)}
}, {})
}
console.log(remove({id: 93961, data}))
your way solution
for (const key in items) {
if (Object.hasOwnProperty.call(items, key)) {
const element = items[key];
element.forEach(x => {
if (x.id === singleItem.id) {
setItems(prevState => ({
...prevState,
//filter will remove the x item
[key]: element.filter(i => i.id !== x.id),
}))
}
})
}
}
short solution.
for(const k in items) items[k] = items[k].filter(x => x.id !== singleItemId);
const items = {
"some_id1": [
{
"id": 93979,
},
{
"id": 93978,
}
],
"some_id2": [
{
"id": 93961,
},
{
"id": 93960,
}
]
}
const singleItemId = 93979;
for (const k in items) items[k] = items[k].filter(x => x.id !== singleItemId);
console.log(items);
//setItems(items)
You could try using the functional update.
const [data, setData] = [{id:1},{id:2},{id:3}...]
Once you know the id which you need to remove.
setData(d=>d.filter(item=>item.id !== id));
I have a nested state like :
bookingDetails = {
jobCards: [
{
details_id: '1',
parts: [
{...},
{...}
]
}
]}
Now I got the respective jobCards in component from props from parent component i.e detailsID by using useSelector:
const jobCard = useSelector(state => state.bookingDetails.jobCards.find(item => item.details_id === detailsID))
I got a button that successfully adds new object in parts in respective jobCards but that doesnot update the UI.
My bookingDetails Reducer:
case 'ADD_PARTS':
return {
...state,
jobCards: state.jobCards.map(jobCard => {
if (jobCard.details_id === action.id) {
jobCard.parts = [...jobCard.parts, { _id: uuid(), name: '' }]
}
return jobCard
})
}
use like this
const [isJobUpdated, setIsJobUpdated] = useState(false);
const jobCard = useSelector(state => state.bookingDetails.jobCards.find(item => item.details_id === detailsID))
useEffect(() => {
setIsJobUpdated(!!jobCard.length);
}, [jobCard])
return (
<>
{isJobUpdated && <YourComponent />
</>
)
NOTE: this is not the best way to do. You might face re-render issue. Just to check if this solve your current issue.
Forgot to add return statement.
The reducer should have been:
case 'ADD_PARTS':
return {
...state,
jobCards: state.jobCards.map(jobCard => {
if (jobCard.details_id === action.id) {
return {
...jobCard,
parts: [...jobCard.parts, { id: uuid(), name: ''}]
}
}
return jobCard
})
}
I am filtering with react over a Json file. How can I do a case insensitive search?
This is my code
_handleSearch ({ inputNameValue, inputPriceValue }) {
let list = data.filter(hotel => hotel.name.includes(inputNameValue))
}
How bout case normalizing both strings, for example lower case them both:
Note that its advised to change the input once outside the filter.
_handleSearch ({ inputNameValue, inputPriceValue }) {
const lowerCased = inputNameValue.toLowerCase();
let list = data.filter(hotel => hotel.name.toLowerCase().includes(lowerCased ))
}
Just enforce the case:
_handleSearch ({ inputNameValue, inputPriceValue }) {
let list = data.filter(hotel =>
hotel.name.toUpperCase().includes(inputNameValue.toUpperCase()))
}
const contacts = [
{
id:1,
name:'khairul Islam',
email:'khairul#gmail.com'
},
{
id:2,
name:'Anisru Rahman',
email:'anisur58#gmail.com'
},
{
id:3,
name:'Raihan',
email:'Raihan#gmail.com'
},
{
id:b,
name:'IB Khalil',
email:'khalil#gmail.com'
}
]
const filterData= 'khairul';
let filterContact = contacts.filter((contact)=>{
return contact.name.toLowerCase().indexOf(filterData.toLowerCase()) >=0;
})