Search functionality in React - javascript

so the problem is I have a search functionality everything works, except that when an item has not been found, you see it should display the text "champion has not been found" but it is not. I would appreciate the help Where am I making a mistake?
import data from './data.json'
import './Champions.css'
import Skills from './Skills'
import CloseIcon from '#material-ui/icons/Close';
const Champions = ({searchValue}) => {
const [toggleShow, setToggleShow] = useState(false);
const [currentSelectedChampion, setCurrentSelectedChampion] = useState({});
const handleSelectChampion = (id) => {
if (!toggleShow) setToggleShow(true);
const currentChampion = data.filter((champ) => champ.id === id)[0];
setCurrentSelectedChampion(currentChampion);
};
function filterChampions(champion) {
return champion.name.toLowerCase().includes(searchValue.toLowerCase());
}
{data.filter(filterChampions).length === 0 && (<div className='not__found'>
<h1>No champion has been found</h1>
</div>)}
return (
<div className="champions">
{data.filter(filterChampions).map((champion) => {
return (
<div key={champion.id} onClick={() => handleSelectChampion(champion.id) } >
<div className="champion">
<img className="champion__Image" src={champion.image}></img>
<h4 className="champion__Name">{champion.name}</h4>
{toggleShow && currentSelectedChampion.id === champion.id && (
<>
<Skills currentChampion={currentSelectedChampion} />
<CloseIcon onClick={() => setToggleShow(false)}/>
</>
)}
</div>
</div>
);
})}
</div>
);
};
export default Champions

The map in line {data.filter(filterChampions).map((champion) => { will not return anything for empty array.
Consider the following examples.
[].map(e => 'called'); // []
[3].map(e => 'called'); // ['called']
So if {data.filter(filterChampions) returns an empty array the map will return empty array and not the div with class not__found.
What you need to do is something as following
const showChamtions = () => {
// Put the filtered data in a variable
const selectedChampions = champions.filter((element) => element.score > 12);
// If data is there do what you intend to do with it else not_found div
if (selectedChampions && selectedChampions.length > 0) {
return selectedChampions.map((element) => <p>{element.name}</p>);
} else {
return (
<div className="not__found">
<h1>No champion has been found</h1>
</div>
);
}
};
Example - https://codesandbox.io/s/map-on-empty-array-i6m1l?file=/src/App.js:349-741
You can modify your code similar to this using a conditional operator as well.

{data.filter(filterChampions).map((champion) => {
if(data.filter || champion){
return (
<div className='not__found'>
<h1>No champion has been found</h1>
</div>
)
}
This if statement is not nesserasy, if an item has not been found => data.filter(filterChampions) will be an empty array, the map function will return nothing, the if statement doesn't even run.
It you want to display the message, you could simply use this:
{data.filter(filterChampions).length === 0 && (<div className='not__found'>
<h1>No champion has been found</h1>
</div>)}

Related

Make other block disappear when chose a value

How can I make other filter button disappear when picked 1 value.
Here is my code base:
const FilterBlock = props => {
const {
filterApi,
filterState,
filterFrontendInput,
group,
items,
name,
onApply,
initialOpen
} = props;
const { formatMessage } = useIntl();
const talonProps = useFilterBlock({
filterState,
items,
initialOpen
});
const { handleClick, isExpanded } = talonProps;
const classStyle = useStyle(defaultClasses, props.classes);
const ref = useRef(null);
useEffect(() => {
const handleClickOutside = event => {
if (ref.current && !ref.current.contains(event.target)) {
isExpanded && handleClick();
}
};
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, [isExpanded]);
const list = isExpanded ? (
<Form>
<FilterList
filterApi={filterApi}
filterState={filterState}
name={name}
filterFrontendInput={filterFrontendInput}
group={group}
items={items}
onApply={onApply}
/>
</Form>
) : null;
return (
<div
data-cy="FilterBlock-root"
aria-label={itemAriaLabel}
ref={ref}
>
<Menu.Button
data-cy="FilterBlock-triggerButton"
type="button"
onClick={handleClick}
aria-label={toggleItemOptionsAriaLabel}
>
<div>
<span>
{name}
</span>
<svg
width="8"
height="5"
viewBox="0 0 8 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.97291 0.193232C7.20854"
fill="currentColor"
/>
</svg>
</div>
</Menu.Button>
<div>
<div>
{list}
</div>
</div>
</div>
);
};
I am trying to achieve when I chose 1 value inside filter block the other block will disappear. Anyone have idea how can I work on this?
I am using React and Redux for this project
Thank you for helping me on this!!!!
Update:
Added parent component for FilterBlock.ks:
const FilterSidebar = props => {
const { filters, filterCountToOpen } = props;
const talonProps = useFilterSidebar({ filters });
const {
filterApi,
filterItems,
filterNames,
filterFrontendInput,
filterState,
handleApply,
handleReset
} = talonProps;
const filterRef = useRef();
const classStyle = useStyle(defaultClasses, props.classes);
const handleApplyFilter = useCallback(
(...args) => {
const filterElement = filterRef.current;
if (
filterElement &&
typeof filterElement.getBoundingClientRect === 'function'
) {
const filterTop = filterElement.getBoundingClientRect().top;
const windowScrollY =
window.scrollY + filterTop - SCROLL_OFFSET;
window.scrollTo(0, windowScrollY);
}
handleApply(...args);
},
[handleApply, filterRef]
);
const [selectedBlock, setSelectedBlock] = useState();
const filtersList = useMemo(
() =>
Array.from(filterItems, ([group, items], iteration) => {
const blockState = filterState.get(group);
const groupName = filterNames.get(group);
const frontendInput = filterFrontendInput.get(group);
if (selectedBlock) {
return (
<FilterBlock
key={group}
filterApi={filterApi}
filterState={blockState}
filterFrontendInput={frontendInput}
group={group}
items={items}
name={groupName}
onApply={handleApplyFilter}
initialOpen={iteration < filterCountToOpen}
iteration={iteration}
id={selectedBlock}
onSelected={setSelectedBlock}
/>
);
}
return (
<FilterBlock
key={group}
filterApi={filterApi}
filterState={blockState}
filterFrontendInput={frontendInput}
group={group}
items={items}
name={groupName}
onApply={handleApplyFilter}
initialOpen={iteration < filterCountToOpen}
iteration={iteration}
id={selectedBlock}
onSelected={setSelectedBlock}
/>
);
}),
[
filterApi,
filterItems,
filterNames,
filterFrontendInput,
filterState,
filterCountToOpen,
handleApplyFilter
]
);
return (
<div className="container px-4 mx-auto">
<Menu
as="div"
className="my-16 justify-center flex flex-wrap py-5 border-y border-black border-opacity-5"
>
{filtersList}
</Menu>
</div>
);
};
console.log(filterItems) and it gave me this output:
Map(3) {'markforged_printer_type' => Array(3),
'markforged_material_filter' => Array(7), 'markforged_parts_filter' =>
Array(7)} [[Entries]] 0 : {"markforged_printer_type" => Array(3)} 1 :
{"markforged_material_filter" => Array(7)} 2 :
{"markforged_parts_filter" => Array(7)}
Updated Answer
From the changes you provided, you are using useMemo() and useCallback(). Those kinds of optimizations in general are not necessary to be made or even decrease performance in some cases. Check this article from Kent C. Dodds (others can be easily found about the theme) to explain some issues with it.
About the changes, as a suggestion, you could use the .map()/.filter() functions instead Array.from().
You are splitting logic about rendering different components with the useMemo(), and this could be changed into one component instead of this whole logic inside the Parent component. (For my suggestion this will be not the case)
As a guide to your code, you could use something like this:
const FilterSidebar = ({ filters, filterCountToOpen }) => {
// here you have the state to control if there is a block selected
const [selectedGroup, setSelectedGroup] = useState();
const {
// only those are needed for this example
filterItems,
handleApplyFilter
} = useFilterSidebar({ filters });
return (
<div className="container px-4 mx-auto">
<Menu
as="div"
className="my-16 justify-center flex flex-wrap py-5 border-y border-black border-opacity-5"
>
{filterItems.map(([group, items], iteration) => {
const groupName = filterNames.get(group);
if (selectedGroup !== null && selectedGroup !== groupName) {
// returning null here should not render anything for this list item
return null;
}
return (
<FilterBlock
// pass all your extra props here
// but the important one is the `onApply`
onApply={(...args) => {
setSelectedGroup((prev) => prev !== null ? null : groupName);
return handleApplyFilter(...args);
}}
/>
);
}}
</Menu>
</div>
);
};
If you see any null on your screen, you could use first the .filter() and then the .map() or combine both with a single .reduce(). It should be something like this:
{filterItems
.filter(([group, items]) => selectedGroup === null || selectedGroup === filterNames.get(group))
.map(([group, items], iteration) => {
const groupName = filterNames.get(group);
return (
<FilterBlock
// pass all your extra props here
// but the important one is the `onApply`
onApply={(...args) => {
setSelectedGroup((prev) => prev !== null ? null : groupName);
return handleApplyFilter(...args);
}}
/>
);
}}
With your update, it is possible to see that you can select by the group (instead of the block which it was called before). Also, you can just add a little change to your onApply prop and that will save and re-render the list. If the selectedGroup is already there, removing the filter will show the other sections. Eventually, you'll need to trim this logic to accommodate other things such as selecting more than one filter and checking for that and so on.
Original Answer
From what you described I'm assuming what you want is: You have 3 FilterBlocks on your screen. Once a user selects one checkbox inside one opened "select" (that you are calling FilterBlock), you want the other FilterBlocks disappear from the screen and just the single FilterBlock with the selected option to stay at the screen (the other 2 will be hidden).
If that's your case, there are some possible options to achieve that but the easiest one is controlling this on a Parent Component: You can pass a prop from the parent component named something like onSelected, give an id to each FilterBlock, and when one filter is selected inside, you trigger that callback with the id from that FilterBlock.
const Parent = () => {
const [selectedBlock, setSelectedBlock] = useState();
if (selectedBlock) {
return <FilterBlock id={selectedBlock} onSelected={setSelectedBlock} />
}
return (
<>
<FilterBlock id="filter-block-1" onSelected={setSelectedBlock} />
<FilterBlock id="filter-block-2" onSelected={setSelectedBlock} />
<FilterBlock id="filter-block-2" onSelected={setSelectedBlock} />
</>
)
}
const FilterBlock = ({ id, onSelected }) => (
return (
<>
<button onClick={() => onSelected(id)}>Select filter block {id}</button>
<button onClick={() => onSelected()}>Unselect filter block {id}</button>
</>
);

My search bar is delaying updating the results

I was making a search bar component that modifies an array and then a mapping function that displays the resulted array it as the page results, the problem is that the page is delaying to update, in other words when I type a character in the search bar nothing changes but when I add another character the results are being updated with the first character input only and the.
I was using a hook state to hold the value of the search input and then using a filter function to update the array, finally I used a mapping function to display the modified array data as card components. As I said the problem is the delay that the website takes to update the array and it seams that the problem is with the state hook I uses but I couldn't solve that problem.
I also reuse the filtered array to display search suggetions
Here is app.js
import React, { useState } from "react";
import ReactDOM from "react-dom/client";
import Card from "./components/Card";
import resourcesData from "./resourcesData";
import { type } from "#testing-library/user-event/dist/type";
function App() {
const [filteredList, setFilteredList] = useState(resourcesData);
const [searchTerm, setSearchTerm] = useState("");
const changeInput = (event) => {
setSearchTerm(event);
};
function handleSearchTerm(event) {
setSearchTerm(event.target.value);
var updatedList = [...resourcesData];
updatedList = updatedList.filter((val) => {
if (searchTerm === "") return val;
else if (
val.title.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
) {
return val;
} else if (
val.thematicArea
.toLocaleLowerCase()
.includes(searchTerm.toLocaleLowerCase())
) {
return val;
}
});
setFilteredList(updatedList);
}
return (
<div className="App">
<input
type="text"
value={searchTerm}
onChange={handleSearchTerm}
className="input"
></input>
<div className="dropdown">
{filteredList.slice(0, 10).map((item) => (
<div
onClick={() => changeInput(item.title)}
className="dropdown-row"
key={item.title}
>
{item.title}
</div>
))}
</div>
<div className="cards">
{filteredList.map((value, index) => (
<Card
resourceURL={value.link}
thumbnailURL=""
title={value.title}
subtitle=""
date={value.date}
description=""
cost=""
cardkeywords={
value.cost === "free"
? [
value.level,
value.language,
value.type,
value.thematicArea,
value.cost,
]
: [
value.level,
value.language,
value.type,
...value.thematicArea.split(","),
]
}
key={index}
/>
))}
</div>
</div>
);
}
export default App;
In the function handleSearchTerm you use setSearchTerm(event.target.value); and after you are using searchTerm which updates asynchronously.
Use in this function event.target.value.
function handleSearchTerm(event) {
const newValue = event.target.value;
setSearchTerm(newValue);
var updatedList = [...resourcesData];
updatedList = updatedList.filter((val) => {
if (newValue === "") return val;
else if (
val.title.toLocaleLowerCase().includes(newValue.toLocaleLowerCase())
) {
return val;
} else if (
val.thematicArea
.toLocaleLowerCase()
.includes(newValue.toLocaleLowerCase())
) {
return val;
}
});
setFilteredList(updatedList);
}

How to call function when state changes except first loading

I want to call function when state data changes but not first loading.
This is my code.
const Page = (props) => {
const { data } = props;
const arrowDirection = (item) => {
if (item.arrow === 1) {
return "up";
} else {
return "down";
}
};
return (
<div>
{data &&
data.map((item, index) => (
<div key={index} className={arrowDirection(item)}>
{item.name}
</div>
))}
</div>
);
};
export default Page;
In here, props data changes automatically every few seconds.
So I want to change classname to up or down according to the status.
But when page loads, I don't want to call arrowDirection function so that the classname to be set as empty.
Eventually, I don't want to set classname for the first loaded data, but for the data from second changes.
How to do this?
I would try to update data in props and for first render let's say have item.arrow === null case for empty class. But if it is not possible, you may use useEffect+useRef hooks:
const Page = (props) => {
const { data } = props;
const d = useRef()
useEffect(() => {
d.current = true
}, []);
const arrowDirection = (item) => {
if (item.arrow === 1) {
return "up";
} else {
return "down";
}
};
return (
<div>
{data &&
data.map((item, index) => (
<div key={index} className={d.current ? arrowDirection(item) : ""}>
{item.name}
</div>
))}
</div>
);
};

Trying to use the new Search API of Stripe with metadata

Currently i'm trying to display some products that are in the Stripe Database. I was trying to do by using if statements, filter and with the new Search API of stripe, but I my mission fail. Some ideia of what could I do?
And Other question, how to I display the feature list of each product?
import Router from "next/router";
export default function Product({ prices, category }) {
return (
<>
<section className="product__container">
{prices.map((price, index) => {
if (price.product.metadata.category === category) {
productCard(price, index);
}
})}
</section>
</>
)}
export function productCard(price, index) {
const amount = price.unit_amount / 100;
return (
<div key={index} onClick={() => Router.push(`/${price.id}`)}>
<img src={price.product.images[0]} alt={price.product.name} />
<p>{price.product.name}</p>
<small>Feature List</small>
<p>€{amount.toFixed(2)}</p>
</div>
);}
I solved the problem like this, if you have better ways I would appreciate it I am open to new possibilities.
import Router from "next/router";
export default function Product({ prices, category }) {
const arr = [];
prices.map((price, index) => {
if (price.product.metadata.category == category && arr) {
arr.push(price);
}
});
return (
<>
<section className="product__container">
{arr.map((price, index) => {
const amount = price.unit_amount / 100;
return (
<div
key={index}
onClick={() => Router.push(`/${price.id}`)}
>
<img
src={price.product.images[0]}
alt={price.product.name}
/>
<p>{price.product.name}</p>
<small>Feature List</small>
<p>€{amount.toFixed(2)}</p>
</div>
);
})}
</section>
</>
);}

Why Isn't My Data Being Rendered in React(NextJS)

I have the following:
const OrderItems = (props) => {
const { Order } = props.res
return (
<div>
{Order.map(order => {
if (order.OrderLines.OrderLine instanceof Array) {
order.OrderLines.OrderLine.map(line => (
<OrderLineItem data={line} />
))
}
})}
</div>
)
}
export default OrderItems
And the component:
const OrderLineItem = ({data}) => {
console.log(data)
return (
<div>Order Line Item</div>
)
}
Nothing is rendered and nothing is logged to the console.
However if I do:
const OrderItems = (props) => {
const { Order } = props.res
return (
<div>
{Order.map(order => {
if (order.OrderLines.OrderLine instanceof Array) {
order.OrderLines.OrderLine.map(line => console.log(line))
}
})}
</div>
)
}
The data I want is logged to the console. How can I render or pass the data from line please?
You don't return from .map() that's the reason. You can use && instead of if.
Try as the following instead:
{Order.map(order =>
order.OrderLines.OrderLine instanceof Array &&
order.OrderLines.OrderLine.map(line => <OrderLineItem data={line} />)
)}
While getting the props in the child component
const OrderLineItem = ({ data }) => {
console.log(data)
// DO WHAT YOU WANT TO DO WITH THE DATA!
return (
<div>Order Line Item</div>
)
}

Categories

Resources