How to make unique groups into MUI's Autocomplete? - javascript

I am using MUI's Autocomplete to group my suggestions inside of search Bar.
I think it seems to be simple but not .
Firstly please look at the shape of my data
[{
"id" : 1,
"bookName" : "it starts with us",
"author" : "Colin Hoover"
},
{ "id" : 2,
"bookName" : "it ends with us",
"author" : "Colin Hoover"
},
{ "id" : 3,
"bookName" : "The great Gatsby",
"author" : "Scott Fitzgerald"
},
{ "id" : 4,
"bookName" : "To Kill a mocking bird",
"author" : "Harper Lee"
},
{ "id" : 5,
"bookName" : "Remainders of Him",
"author" : "Colin Hoover"
}, ... ]
now you can see, there are 3 books written by 'Colin Hoover', placed at different Ids. What I want to achieve is, my MUI's autocomplete should render
Colin Hoover //author name as heading and books underneath it
Remainders of Him
It Ends with us
It starts with us
... other books from this specific author
into my groupBy attribute in MUI's Autocomplete.
Issue I am having now - When I arrange the books which are meant for a certain author in consecutive Id's (say, id: 1, 2, 3, 4 ..) , the group-by of autocomplete works fine (i.e, it groups the different books under the same author if the author occurs at consecutive ids). But, when I arrange them in random fashion ( ie, id 1,3, 5, 8, 9 ..occupies books under same name "Colin Hoover"), then it doesn't group any of it rather it creates duplicate headers as "Colin Hoover" into autosuggestion list, which I don't want.
ALl I want is same header and all of his/her written books under his name into MUI's autocomplete.
Here's my Code
imports ...
export default function JobSearch () {
const [ books, setBooks ] = useState<Book[]>([]);
const [ searchTerm, setSearchTerm ] = useState<string>('');
// using redux
const dispatch = useAppDispatch();
const { status } = useAppSelector ( state => state.menu );
useEffect(() => {
agent.Books.getBooks() // my endpoint for getting the books
.then((books) => { setBooks(books);
console.log(books)})
.catch((error) => console.log(error))
}, []);
const suggestions = books.map((book) => {
const bookDetail = `${book.name} by ${book.author}`
return {
bookDetail,
...book
}});
const handleChangeInput = ( event : any ) => {
setSearchTerm( event.target.value);
console.log(event.target.value);
}
return (
<Fragment>
<Autocomplete
open = { status.name === "autoCompleteMenu" && status.open }
autoHighlight = {true}
autoComplete = {true}
noOptionsText = {`Search for ${searchTerm}`}
onOpen = { () => {
dispatch(setOpenMenu("autoCompleteMenu"));
}}
onClose = { () => dispatch(setCloseMenu("autoCompleteMenu"))}
options = {suggestions.sort((a, b) => -b.bookDetail.localeCompare(a.bookDetail))}
isOptionEqualToValue = {(option, value) => option.bookDetail === value.bookDetail}
groupBy = {(option) => option.author} // I want all books sorted under same author
getOptionLabel={(option) => option.bookDetail}
renderInput = {(params) => (
<TextField { ...params}
label = {'search ...'}
onChange = {handleChangeInput}
variant = {'outlined'} /> )} />
</Fragment>
)
}
I know I have to implement 'sort' into groupBy, but how to do the same, so as to sort all the books according to the author name and pass it as a parameter of the groupBy attribute.
All Suggestions are welcome and highly appreciated.

Related

How can I do dynamic badge on React CoreUI?

I have a problem with a badge on Core UI. I have a Sidebar and one of the elements is Chat. When a new message comes, the badge must show new message. But the old developer have written different ways and I can not change it. I cannot find anything for this.
My codes
Sidebar elements
const _nav = [
{
_tag: "CSidebarNavItem",
name: "Chat",
to: "/chat",
filter: "feedbacks",
icon: "cil-speech",
badge: {
color: "info",
text: "NEW MESSAGE",
},
},
]
My React component
import navigation from "./_nav";
const [filteredNav, setFilteredNav] = useState([]);
const [chatBadge, setChatBadge] = useState(false);
const handleChatBadge = () => {
setChatBadge(true)
}
// it is a sidebar element for user-role
useLayoutEffect(() => {
allwedMenu().then((res) => {
const arr = [navigation[0]];
res.data.items.forEach((element) => {
arr.push(element.name);
});
setFilteredNav(navigation.filter((item) => arr.includes(item.filter)));
});
}, []);
<CSidebarNav>
<CCreateElement
items={filteredNav}
components={{
CSidebarNavDivider,
CSidebarNavDropdown,
CSidebarNavItem,
CSidebarNavTitle,
}}
/>
</CSidebarNav>
I need the badge to show when chatBadge is true. But I don't know how can I write this.
You can only add a condition to show the badge when chatBadge is true.
Based on the Value of ChatBadge, you can use the property of the Component CSideBarNavItem to display the badge and pass the colour and text properties.
Here's the updated code:
<CSidebarNav>
<CCreateElement
items={filteredNav}
components={{
CSidebarNavDivider,
CSidebarNavDropdown,
CSidebarNavItem,
CSidebarNavTitle,
}}
/>
{filteredNav.map((item, index) => (
<CSidebarNavItem
key={index}
name={item.name}
to={item.to}
icon={item.icon}
badge={
item.name === "Chat" && chatBadge
? { color: "info", text: "NEW MESSAGE" }
: null
}
/>
))}
</CSidebarNav>
Hope it helps.

React use True/False for .filter.map

I have a site, where i want so filter my pictures via tags.
the filter is made with checkboxes, which are generated by the tags themselves.
i break down the code to the nessessary:
const [filteredTags, setFilteredTags] = useState();
// {wedding: false, couple: true, NSWF: true} <-this is the ouput of console.log(filteredTags)
const PortfolioData= [
{
name: "John & Johna ",
tag: ["wedding", "couple"],
img: [ src1 , src2, ...] //irrelevant
},
{
name: "Mario & Marie",
tag: ["couple", "NSFW"],
img: [ src1 , src2, ...] //irrelevant
},
];
return(
<>
{PortfolioData
.filter(visible => visible.tag.includes( ??????? )) //<- what to write ?
.map((PortfolioData) => {
return (
<Tile
header={PortfolioData.name}
imgSrc={PortfolioData.mainImg[0]}
/>
);
})}
</>
)
How can I filter this?
Check that .some of the tags being iterated over are truthy in the filteredTags object. (To make things easier to manage, it'd be good to set filteredTags to also have an initial value of an empty object, and not undefined)
const [filteredTags, setFilteredTags] = useState({});
// for TypeScript:
// const [filteredTags, setFilteredTags] = useState<Record<string, boolean | undefined>>({});
{PortfolioData
.filter(collection => collection.tag.some(tag => filteredTags[tag]))

Take the value of the attribute, in the map, but with names of different attributes in javascript

I'm new here on the site, I have a question regarding a function in react that I am unable to resolve. The solution is very simple but it does not work for me.
I build a social network in React, in the social network I have users, each user has a different name, it's like a key.
I have a match object, which contains matches between one user, represented by handle, and the other users represented by match_(number).
Because each of the users' names is unique, I want to use them in the key.
The problem is that for each user the name of the atribute is different, and I can not think of a way, to be able to access in a loop for all users.
match =
[
{handle: "backend4" , ...},
{match_1: "backend1" , ...},
{match_2: "backend2" , ...},
{match_3: "backend3" , ...},
{match_4: "devops1" , ...},
{match_5: "devops2" , ...},
{match_6: "mobile2" , ...},
{match_7: "test500" , ...},
{match_8: "testing100" , ...},
{match_9: "testing200" , ...},
{match_10: "testtttttt" , ...},
];
recentMatchesMarkup = match.map((singleMatch) =>
<Match key={singleMatch.id} matchInfo={singleMatch} />)
);
I'm sure the problem is very small, but I can 't think of how to deal with it
Here is how you could access the value of match_x, x being the index of the loop :
match.map((el, i) => console.log(el[match_${i}]))
(There is the special quote wrapping match_${i} above. I just can't display it since they are used to display code.. see this link if you don't know about it).
But note that is only works with a key match_x so if you are 100% sure it's ok for you, go for it.
The first key handle won't be displayed (return undefined) so you might need to add a condition to display your <Match> component.
To be sure each match correspond to the index, here is an example on Stackblitz about how you could do it, and here is the code :
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const [sortedMatch, setSortedMatch] = React.useState([]);
const match = [
{ handle: "backend4" },
{ match_2: "backend2" },
{ match_3: "backend3" },
{ match_1: "backend1", test: "text" },
{ match_4: "devops1" },
{ match_5: "devops2" },
{ match_6: "mobile2" },
{ match_7: "test500" },
{ match_8: "testing100" },
{ match_9: "testing200" },
{ match_10: "testtttttt" }
];
React.useEffect(() => {
const matchByIndex = [];
match.forEach(el => {
const key = Object.keys(el).find(k => k.includes("match"));
if (key) {
let nb = key.split("_");
nb = nb[nb.length - 1];
matchByIndex[nb] = el;
}
});
setSortedMatch(matchByIndex);
}, []);
if (!sortedMatch.length) return <>Loading...</>;
return (
<div>
{sortedMatch.map((el, i) => (
<>
{el[`match_${i}`]}
<br />
</>
))}
</div>
);
};
render(<App />, document.getElementById("root"));

Filtering function running one step too late

I'm building a component to filter an array of data.
There are 2 filters, one for the Team and one for the location. When each of the dropdowns are changed I'm changing a state variable for team and location and then running the filter function.
For some reason, the filter is running one step too late.
As an example - if I choose Team A, nothing will update. If I then choose Team B the careersDataFiltered variable will show Team A. If I choose Team C it'll then show Team B's data.
Its almost like its running one step too late.
You can see from the code that I'm running the filter after the state variables have been set which is why this is a bit of head-scratcher for me.
import React, { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
const careersData = [
{
name: "X Job Title",
url: "/url/x",
team: "Team a",
location: "London",
},
{
name: "M Job Title",
url: "/url/m",
team: "Team a",
location: "London",
},
{
name: "B Job Title",
url: "/url/b",
team: "Team c",
location: "Sheffield",
},
{
name: "A Job Title",
url: "/url/a",
team: "Team b",
location: "London",
},
{
name: "F Job Title",
url: "/url/f",
team: "Team b",
location: "Sheffield",
},
{
name: "C Job Title",
url: "/url/c",
team: "Team c",
location: "London",
},
{
name: "Q Job Title",
url: "/url/q",
team: "Team a",
location: "Sheffield",
},
]
const uniqueTeams = []
const uniqueLocations = []
// Build the unique values
if (careersData !== null) {
careersData.map(career => {
if (uniqueTeams.indexOf(career.team) === -1) {
return uniqueTeams.push(career.team)
}
})
}
if (careersData !== null) {
careersData.map(career => {
if (uniqueLocations.indexOf(career.location) === -1) {
return uniqueLocations.push(career.location)
}
})
}
// reorder ready for output
uniqueTeams.sort()
uniqueLocations.sort()
const CurrentVacancies = () => {
const [careersDataFiltered, setCareersDataFiltered] = useState(careersData)
const [filterTeam, setFilterTeam] = useState("")
const [filterLocation, setFilterLocation] = useState("")
// filter array based on values
const runFilter = () => {
// reset the filter data
setCareersDataFiltered(careersData)
if (filterTeam !== "") {
setCareersDataFiltered(
careersDataFiltered.filter(career => career.team === filterTeam)
)
}
if (filterLocation !== "") {
careersDataFiltered(
careersDataFiltered.filter(career => career.location === filterLocation)
)
}
console.log(careersDataFiltered)
}
return (
<>
<div className="flex">
<h2 className="mr-auto">Current Vacancies</h2>
<div className="">
<select
onChange={e => {
setFilterTeam(e.target.value)
runFilter()
}}
>
<option value="">Team</option>
{uniqueTeams.map(team => (
<option key={team} value={team}>
{team}
</option>
))}
</select>
<select
onChange={e => {
setFilterLocation(e.target.value)
runFilter()
}}
>
<option value="">Location</option>
{uniqueLocations.map(location => (
<option key={location} value={location}>
{location}
</option>
))}
</select>
</div>
</div>
<div>
<AnimatePresence>
{careersDataFiltered.map((career, index) => (
<motion.div
key={index}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
positionTransition
>
<div>
<div className="text-sm">
{career.name} - {career.team} | {career.location}
</div>
</div>
</motion.div>
))}
</AnimatePresence>
</div>
</>
)
}
export default CurrentVacancies
First of all Your code has some errors, you should use setCareersDataFiltered() in second if statement of runFilter(). Also setCareersDataFiltered() is async so you can not use careersDataFiltered right after you call setCareersDataFiltered(). It's same for call to setFilterTeam() because you are using filterTeam in runFilter() so you can not call runFilter() after your call to setFilterTeam() because your data is not guaranteed to be updated. It's also true for setFilterLocation().
Put runFilter() inside useEffect() so it will call it when any dependencies of runFilter() changes.
useEffect(() => {
runFilter();
}, [runFilter]);
Also change runFilter() as follow and use useCallback() to prevent re-render loop.
// filter array based on values
const runFilter = useCallback(() => {
let filter = careersData;
if (filterTeam !== "") {
filter = filter.filter(career => career.team === filterTeam);
}
if (filterLocation !== "") {
filter = filter.filter(career => career.location === filterLocation);
}
setCareersDataFiltered(filter);
}, [filterLocation, filterTeam]);
At the end remove any manual call to runFilter();
As a hint I think your filter logic has a bug also because I can set team to A and then it only show me A but if I set location it apply location filter and ignore A filter and it's because of async issue. So if you want to apply both filters at the same time you should mix two if statements in runFilter(). I've mixed filters and wrote a new filter routine.
Here is my working demo version
Ciao, this problem could be related on how you set careersDataFiltered in function runFilter. For my personal experience, read and set state directly is never a good idea. Try to change runFilter like this:
const runFilter = () => {
let temp_careersDataFiltered = _.cloneDeep(careersData); //here you have to clone deep careersData because javascript keep reference (I use cloneDeep from lodash)
if (filterTeam !== "") {
temp_careersDataFiltered = careersData.filter(career => career.team === filterTeam)
}
if (filterLocation !== "") {
temp_careersDataFiltered = careersData.filter(career => career.location === filterLocation)
}
setCareersDataFiltered(
temp_careersDataFiltered
)
console.log(temp_careersDataFiltered)
}
Explanation: since you don't have certainty that after call setCareersDataFiltered value of careersDataFiltered is immediately available, apply filters to temp_careersDataFiltered temporary array and in the end set careersDataFiltered to re-render the component.
I'm cloning careersData because you know javascript works with reference and if you write let temp_careersDataFiltered = careersData if you modify temp_careersDataFiltered infact you are modifying careersData also.

React Native Firebase search listview

I have a listview that is hydrated from my Firebase in my React Native app. I have came across a few tutorials on how people search listview's with the fetch function but nothing for Firebase.
I have a search bar at the header of my listview, how can I search by keyword and filter the results using Firebase?
I followed the guide on Firebase's website to setup my listview and datasource. I want to be able to search by keyword in each of those fields
listenForItems(itemsRef) {
itemsRef.on('value', (snap) => {
var items = [];
snap.forEach((child) => {
items.push({
title: child.val().title,
category: child.val().category,
description: child.val().description,
});
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(items)
});
});
}
I finally figured this out. This code searches based on a specific item name.
Database
{
"directory" : {
"macintosh" : {
"address" : "117 East 11th Street, Hays, KS 67601",
"firstLetter" : "m",
"title" : "Macintosh",
},
"apple" : {
"address" : "117 East 11th Street, Hays, KS 67601",
"firstLetter" : "a",
"title" : "apple",
},
},
ListView w/Search Bar
render() {
return (
<View style={styles.container}>
<ScrollView>
<SearchBar
returnKeyType='search'
lightTheme
placeholder='Search...'
onChangeText={(text) => this.setState({searchText:text})}
onSubmitEditing={() => this.firstSearch()}
/>
{
this.state.loading &&
<ActivityIndicator
size="large"
color="#3498db"
style={styles.activityStyle}
/>
}
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderItem.bind(this)}
enableEmptySections={true}
/>
</ScrollView>
</View>
);
}
Search Function
firstSearch() {
this.searchDirectory(this.itemsRef);
}
searchDirectory(itemsRef) {
var searchText = this.state.searchText.toString();
if (searchText == ""){
this.listenForItems(itemsRef);
}else{
itemsRef.orderByChild("searchable").startAt(searchText).endAt(searchText).on('value', (snap) => {
items = [];
snap.forEach((child) => {
items.push({
address: child.val().address,
firstLetter: child.val().firstLetter,
title: child.val().title,
});
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(items)
});
});
}
}
This searches the firebase by replacing the listview datasource by using the orderbychild.startat().endat() firebase function. It's the closest thing to a "SELECT * BY %LIKE%" sql search functions.
I modified my code to search by the first letter as well. So incase someone searches for "Macbuk", it will still show the "Macbook" result. It only searches the M. I added another field in my database to the first letter of the title to search and made it lowercase.

Categories

Resources