React hook page throws 'filter' of undefined (anonymous) exception and unable to perform search - javascript

I have implemented a React hookhome page where I have got list of players displaying, I have added an input search to search the players based on the player name or Position, but it throws below exception,could someone please advise the cause of exception;
TypeError: Cannot read property 'filter' of undefined (anonymous
function) C:/Project1/soccerpep/src/components/Home.js:18 15 |
setSearchTerm(event.target.value); 16 | }; 17 | React.useEffect(()
=> {
18 | const results = playerList.name.filter(player =>
| ^ 19 | player.toLowerCase().includes(searchTerm) 20 | ); 21 | setSearchResults(results);
The react hook page follows below:
import React, { useEffect, useState } from "react";
import { Link } from 'react-router-dom';
var playerList = [
{ image: '/images/person.png', name: 'Player 1', position: "Forward" },
{ image: '/images/person.png', name: 'Player 2', position: "Defense" },
{ image: '/images/person.png', name: 'Player 3', position: "Mid Fielder" },
{ image: '/images/person.png', name: 'Player 4', position: "Forward" }
];
const Home = () => {
const [searchTerm, setSearchTerm] = React.useState("");
const [searchResults, setSearchResults] = React.useState([]);
const handleChange = event => {
setSearchTerm(event.target.value);
};
useEffect(() => {
const results = playerList.name.filter(player =>
player.toLowerCase().includes(searchTerm)
);
setSearchResults(results);
}, [searchTerm]);
return (
<div className="App">
<div className="wrapper">
<div className="playerList_header">
<h1>Players</h1>
<label>
<div className="playerSearch_Home">
<div className="playerSearch_Icon">
<img src="/images/search-image-player.jpg"></img>
</div>
<input type="text" className="playerSearch_Home_Input" placeholder="Search players..." value={searchTerm} onChange={handleChange}/>
</div>
</label>
</div>
<div className="playerList_home_page">
<div className="grid-container">
{
playerList.map(player => {
return (
<div className="grid-item">
<div>
<img className="playerProfilePic_home_tile" key={player.image} src={player.image}></img>
</div>
<div className="playerProfile_grid_border">
<h3 key={player.name}>{player.name}</h3>
<span className="playerPosition_home_tile" key={player.position}>{player.position}</span>
</div>
</div>
);
})
},
{
searchResults.map(player => {
return (
<div className="grid-item">
<div>
<img className="playerProfilePic_home_tile" key={player.image} src={player.image}></img>
</div>
<div className="playerProfile_grid_border">
<h3 key={player.name}>{player.name}</h3>
<span className="playerPosition_home_tile" key={player.position}>{player.position}</span>
</div>
</div>
);
})
}
</div>
</div>
</div>
</div>
);
}
export default Home;

playerList is an array, not an object, it doesn't have property name:
const results = playerList.filter(player =>
player.name.toLowerCase().includes(searchTerm) || player.position.toLowerCase().includes(searchTerm)
);

.filter() can be used only on arrays, you tried to run on name property which does not exist.
Instead you can use as the following:
var playerList = [
{ image: '/images/person.png', name: 'Player 1', position: "Forward" },
{ image: '/images/person.png', name: 'Player 2', position: "Defense" },
{ image: '/images/person.png', name: 'Player 3', position: "Mid Fielder" },
{ image: '/images/person.png', name: 'Player 4', position: "Forward" }
];
const searchTerm = 'Player 2';
const results = playerList.filter(p => p.name.toLowerCase() === searchTerm.toLowerCase());
console.log(results);
I hope this explains!

Related

Getting Undefined while accessing the values of data object inside map in Next.js

// while accessing the object values from data, I'm getting undefined in map
// ../data/section1
const data = [{
id: 1,
image: './images/homepage/xbox-games.png',
text: 'Buy Xbox games and consoles',
}, {
id: 2,
image: './images/homepage/shop_surface_devices.webp',
text: 'Shop surface devices',
}, {
id: 3,
image: './images/homepage/choose_your_ms_365.png',
text: 'Choose your Microsoft 365',
}, {
id: 4,
image: './images/homepage/shop_windows_10.png',
text: 'Shop Windows 10',
}]
export default data;
// the actual component
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => {
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
})}
</div>
</>
)
}
export default Section1;
return JSX from the map
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => {
return (
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
)
})}
</div>
</>
)
}
export default Section1;
I had the same problem, then tried the first bracket instead of the second, and it resolved the problem
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => (
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
))}
</div>
</>
)
}
export default Section1;

React js ecommerce price change when size and material variants option selected

I am trying to get the final price in the productInfo page when the material and size variants options are selected.
In the data.js:
export const productItems = [
{
id: 1,
image: ["../Images/Prod1/Swiming_fish_W.png","../Images/Prod1/Ocean Fish2.png"],
title: "Swim Fish",
price:49.99,
desc: "Simplicty, Naturality, and Humanity of Art",
variants: [{material: "Stretched Canvas", size: "12x16 inches", price: 49},
{material: "Stretched Canvas", size: "15x20 inches", price: 69},
{material: "Fine Art Print", size: "12x16 inches", price:39},
{material: "Fine Art Print", size: "15x20 inches", price: 59},
],
},
{
id: 2,
image: ["../Images/Prod2/Duckling_W.png","../Images/Prod2/Duckling 2.png"],
title: "Bath Duckling",
price:49.99,
desc: "Simplicty, Naturality, and Humanity of Art",
variants: [{material: "Stretched Canvas",size: "20x24 inches", price: 69},
{material: "Stretched Canvas",size: "24x30 inches", price: 79},
{material: "Fine Art Print", size: "20x24 inches", price:59},
{material: "Fine Art Print", size: "24x30 inches", price: 69},
],
}]
In the detailProductPage.js
import React, { useEffect, useState } from 'react';
// import { Col, Row } from 'antd';
import ProductImages from './ProductImages';
import ProductInfo from './ProductInfo';
import {productItems} from '../Data';
import { useParams } from 'react-router-dom';
import './DetailProductPage.css';
function DetailProductPage() {
const { id }=useParams();
const [details, setDetails]=useState([])
useEffect(() => {
setDetails(productItems.find(item => String(item.id) ===id ))
}, [id]);
return (
<div className="postPage" style={{width:'100%', padding: '5rem 3rem'}}>
<div className="detailInfo">
<div className="detailLeft">
<ProductImages detail={details} />
</div>
<div className="detailRight">
<ProductInfo detail={details} />
</div>
</div>
</div>
)
}
export default DetailProductPage
Finally in the ProductInfo.js
import React, {useEffect, useState} from 'react';
import { useStateValue } from '../StateProvider';
import './ProductInfo.css';
import Select from 'react-select';
function ProductInfo(props) {
const [Product, setProduct] = useState({})
const [{basket},dispatch]=useStateValue()
const [size, setSize] = React.useState();
const [material, setMaterial] = React.useState();
const [price, setPrice] = React.useState();
console.log(props.detail.variants)
const materialOptions = props.detail.variants
.map((p) => p.material)
.filter((v, i, a) => a.indexOf(v) === i)
.map((material) => ({ label: material, value: material }))
console.log(materialOptions)
const sizeOptions = props.detail.variants
.map((p) => p.size)
.filter((v, i, a) => a.indexOf(v) === i)
.map((size) => ({ label: size, value: size }));
console.log(sizeOptions)
const priceOptions = props.detail.variants
.filter((p) => size && p.size === size.value && material && p.material=== material.value)
.map((p) => p.price)
.filter((v, i, a) => a.indexOf(v) === i)
.map((price) => ({ label: price, value: price }));
console.log(priceOptions.length)
useEffect(()=>{
setProduct(props.detail)
}, [props.detail])
return (
<div className='prod__info'>
<h1>{Product.title}</h1>
<h5 className='prod__desc'>Description</h5>
<p className='prod__text'>{Product.desc}</p>
<p className='prod__price'>
<small>$</small>
if (priceOptions.length===1){
priceOptions.value
} else {Product.price}
</p>
<br />
<p className='prod__desc'>Material</p>
<div style={{boxShadow: '0 5px 5px #939596',cursor:'pointer',borderRadius: '10px!'}}>
<Select value={material} onChange={setMaterial} options={materialOptions} />
</div>
<p className='prod__desc'>Size</p>
<div style={{boxShadow: '0 5px 5px #939596',cursor:'pointer',borderRadius: '10px!'}}>
<Select value={size} onChange={setSize} options={sizeOptions} />
</div>
<br />
<br />
<div className='button__cart'>
<button className='prod__button'
onClick={addToBasket}
>
Add to basket
</button>
</div>
</div>
)
}
export default ProductInfo
I have problems in getting the final price change after material and size variant options selected in ProductInfo.js
<p className='prod__price'>
<small>$</small>
if (priceOptions.length===1){
priceOptions.value
} else {Product.price}
</p>
For reference, my website is weiwhite.com, clicking on one of the painting for the product detail page.
In productInfo.js add a ? in front of .map:
//...
let finalPrice={}
if (priceOptions?.length === 1) {
finalPrice = priceOptions.value
} else {
finalPrice = Product.price
}
//...
return
//...
<strong> finalPrice </strong>
//...
The question mark creates an Optional Chain.
The optional chaining operator (?.) enables you to read the value of a property located deep within a chain of connected objects without having to check that each reference in the chain is valid. - MDN

Why am I unable to target a mapped out <li> from the button mapped out with it with React?

I am in the process of learning react and am working on a pretty simple food ordering app that maps through a list of meals in a component named AvailableMeals and would allow a user to hit the add to cart button on each mapped out item to add it to cart.
Right now though, I am unable to get my add button to target the LI that it is connected to. I have tried putting the targeting function in multiple places and tried to target different things (this, this.name, e.target.value, etc) but everything still comes up with undefined or throws an unable to read undefined error. I have even tried to use useContext, but still nothing. I will add the components that are relevant to this below.
My component tree is MealForm -> MealItem -> MealItemForm -> Input and will paste the markup here in that order.
Thanks in advance for any help.
const MealForm =(props) => {
return (
<ul className='mealForm'>
<MealItem meals={AvailableMeals} />
</ul>
)
}
const MealItem =(props) => {
// const mealsCtx = useContext(AvailableMeals);
const [cart, setCart] = useState(null)
const [curMeal, setCurMeal] = useState(null)
const addToCartHandler = (e, props) => {
e.preventDefault();
console.log(this); //This is what I am using to try and target
}
if ( props.meals.length === 0) {
return( <h2>No meals found!!</h2>)
} else{
return (
<Card >
{AvailableMeals.map((meal)=> {
return<>
<ul className='mealItem'>
<div className='mealItem-info'>
<li>{meal.name}</li>
<li>{meal.description}</li>
<li>{meal.price}</li>
<MealItemForm
id={meal.id}
key={meal.id}
name={meal.name}
description={meal.description}
onSub={addToCartHandler}
/>
</div>
</ul>
</>
})};
</Card>
)
}
};
const MealItemForm = (props) => {
return(
<form onSubmit={props.onSub}>
<Input label="Amount " input={{
id: 'amount_' + props.id,
type: 'number',
min: '1',
max: '10',
step: '1',
defaultValue: '1'
}}
/>
<button> + Add</button>
</form>
)
}
const Input = (props) => {
return (
<div >
<label htmlFor={props.input.id}>{props.label}</label>
<input {...props.input}/>
</div>
)
}
One is able to use stack-snippets to provide a working demo of one's code. This answer addresses the OP's question in trying to identify which meal's click event was invoked.
Changes significant to the question:
On the component: MealItemForm, changed below:
<form name={props.name} onSubmit={props.onSub}>
On the component: MealItem, changed addToCartHandler method:
console.log('mean-name corresponding to the clicked-item: ', e.target.name);
Impact observed:
The console.log properly displays the meal.name corresponding to the item's whose button was clicked.
Code Snippet
const {useState} = React;
const Input = (props) => {
return (
<div >
<label htmlFor={props.input.id}>{props.label}</label>
<input {...props.input}/>
</div>
)
};
const MealItemForm = (props) => {
return(
<form name={props.name} onSubmit={props.onSub}>
<Input label="Amount " input={{
id: 'amount_' + props.id,
type: 'number',
min: '1',
max: '10',
step: '1',
defaultValue: '1'
}}
/>
<button> + Add</button>
</form>
)
};
const MealItem = (props) => {
const [cart, setCart] = useState(null);
const [curMeal, setCurMeal] = useState(null);
const addToCartHandler = (e, props) => {
e.preventDefault();
console.log('mean-name corresponding to the clicked-item: ', e.target.name);
};
if ( props.meals.length === 0) {
return (<h2>No meals found!!</h2>)
} else {
return (
<div>
{
props.meals.map(meal => {
return (
<div>
<ul className='mealItem'>
<div className='mealItem-info'>
<li>{meal.name}</li>
<li>{meal.description}</li>
<li>{meal.price}</li>
<MealItemForm
id={meal.id}
key={meal.id}
name={meal.name}
description={meal.description}
onSub={addToCartHandler}
/>
</div>
</ul>
</div>
)
})
}
</div>
)
}
};
const AvailableMeals = [
{name: 'name 0', description: 'description 0', price: 'price 0', id: '0' },
{name: 'name 1', description: 'description 1', price: 'price 1', id: '1' },
{name: 'name 2', description: 'description 2', price: 'price 2', id: '2' },
{name: 'name 3', description: 'description 3', price: 'price 3', id: '3' },
{name: 'name 4', description: 'description 4', price: 'price 4', id: '4' }
];
const MealForm =(props) => {
return (
<ul className='mealForm'>
<MealItem meals={AvailableMeals} />
</ul>
)
};
ReactDOM.render(
<div>
DEMO
<MealForm />
</div>,
document.getElementById("rd")
);
.mealItem-info {
display: flex; border: 2px solid grey; width: fit-content;
align-items: center;
justify-content: space-around;
margin: 5px;
}
.mealItem-info>li { padding: 20px; margin-left: 25px; }
<div id="rd" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>

How to change specific item button name by clicking the button in ReactJs?

I have multiple item cards on a page, I want to change only a specific item/card button " Add Favt" to "Remove Favt" when the user clicks on the "Add Favt" button. But in my case, all of the card button names change when clicked on only one card button.
Here is my approach:
const Listitem = ({ posts }) => {
const [btn, setBtn] = useState('Add Favt');
var arr = [];
const click = (index) => {
arr.push(posts[index]);
console.log(arr);
localStorage.setItem('items', JSON.stringify({ arr }));
if (btn === 'Add Favt') {
setBtn('Remove Favt');
} else {
setBtn('Add Favt');
}
};
return (
<div className="fav-content">
<ul className="card">
{posts.map((item, index) => {
console.log(item._id);
return (
<li key={item._id}>
<button onClick={() => click(index)}>{btn}</button>
<div className="post">
<h1>Name: {item.name}</h1>
<p>Bio: {item.bio}</p>
<a href={item.link}>Link: {item.link}</a>
</div>
</li>
);
})}
</ul>
</div>
);
};
How to solve this problem?
This may be one possible solution to achieve the desired objective.
Code Snippet
Please view the snippet in Full Page
const {useState} = React;
const Listitem = ({posts, ...props}) => {
// track whether each card is is favorite or not
const [isFavorite, setIsFavorite] = useState({});
// when button clicked, flip card from favorite
const handleClick = e => {
const id = e.target.id;
setIsFavorite(prev => ({
...prev,
[id]: !prev[id]
}))
};
return (
<div className="fav-content">
List of favorites: {
posts
.filter(({_id}) => [_id] in isFavorite && isFavorite[_id])
.map(({name}) => name)
.join()
}
<ul className="card">
{posts.map(item => (
<li key={item._id}>
<button
id={item._id}
onClick={handleClick}
>
{
isFavorite[item._id]
? 'Remove Fav'
: 'Add Fav'
} {item.name}
</button>
<div className="post">
<h4>Name: {item.name}</h4>
<p>Bio: {item.bio}</p>
<a href={item.link}>Link: {item.link}</a>
</div>
</li>
)
)}
</ul>
</div>
);
};
const itemsList = [
{_id: 1, name: 'item 1', bio: 'bio 1', link: 'link 1'},
{_id: 2, name: 'item 2', bio: 'bio 2', link: 'link 2'},
{_id: 3, name: 'item 3', bio: 'bio 3', link: 'link 3'},
{_id: 4, name: 'item 4', bio: 'bio 4', link: 'link 4'},
];
ReactDOM.render(
<div>
DEMO
<Listitem posts={[...itemsList]}/>
</div>,
document.getElementById("rd")
);
.fav-content { width: fit-content; padding: 5px 15px; }
.card { background-color: #DDDDFF; margin: 5px 15px; }
.post { background-color: #FFFFDD; margin: 5px 15px; }
<div id="rd" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
Explanation
Inline comments have been provided in the above code snippet.
You are using btn state variable for each button. setting btn in state will reflect in all of them. Make a separate component for this.
<button onClick={() => click(index)}>{btn}</button>
<div className="post">
<h1>Name: {item.name}</h1>
<p>Bio: {item.bio}</p>
<a href={item.link}>Link: {item.link}</a>
</div>
Maintain a local state in new component for status of individual item.

Implementing pagination in React that displays 15 items per page from a JSON file

I am having issues on even trying to get started with doing pagination without the use of any packages. I am pulling data from a JSON file that contains about 30-32 quotes. I need 15 quotes per page to be displayed and have no idea how to even do that using React. So far what I have is all the quotes being displayed by default. I have three buttons, each filters through the JSON to provide quotes by the theme of the quote which is displayed by the button. This is how far I got:
class App extends Component {
constructor(props) {
super(props);
this.state ={
results: quotes,
search: ""
}
}
gameFilterClick = event => {
event.preventDefault();
const games = [];
for(let i = 0; i < quotes.length; i++){
if (quotes[i].theme === "games"){
games.push(quotes[i])
}
}
this.setState({results: games})
}
movieFilterClick = event => {
event.preventDefault();
console.log('blah!!')
const movies = [];
for(let i =0; i < quotes.length; i++){
if(quotes[i].theme === 'movies'){
movies.push(quotes[i])
}
}
this.setState({results: movies})
}
allButtonClick = event => {
this.setState({results: quotes})
}
quoteSearch = query => {
let search = quotes.map
}
render() {
return (
<div className="App">
<h1>Quotes</h1>
<Search />
<div id='buttons'>
Filters:
<button onClick={this.allButtonClick}>All Quotes</button>
<button onClick={this.gameFilterClick}>Games</button>
<button onClick={this.movieFilterClick}>Movies</button>
</div>
<div id='resultsDiv'>
<Results
results={this.state.results}
/>
</div>
</div>
);
}
}
export default App;
I would recommend using react-bootstrap for this. You'll need to install two packages (they use to come in one, but now pagination package is separated):
react-bootstrap-table-next
react-bootstrap-table2-paginator
So, let's install them:
npm i --save react-bootstrap-table-next
npm i react-bootstrap-table2-paginator
And here goes a simple example of implementation:
import BootstrapTable from 'react-bootstrap-table-next';
import paginationFactory from 'react-bootstrap-table2-paginator';
// Let's imagine this is your JSON data
const yourJsonData = [{id: 1, author: "David Goggins", quote: "Life goes on"},
{ id: 2, author: "Robert Green", quote: "yes it does"}]:
// Here we define your columns
const columns = [{
dataField: 'author',
text: 'AUTHOR'
}, {
dataField: 'quote',
text: 'QUOTE'
}];
// Give it an option to show all quotes
let allQuotes = Number(yourJsonData.length);
// Set all of the major pagination options. You can reduce them if you want less
const options = {
paginationSize: 15,
pageStartIndex: 0,
firstPageText: 'First',
prePageText: 'Back',
nextPageText: 'Next',
lastPageText: 'Last',
nextPageTitle: 'First page',
prePageTitle: 'Pre page',
firstPageTitle: 'Next page',
lastPageTitle: 'Last page',
sizePerPageList: [{
text: 'show 15', value: 15
}, {
text: 'show 30', value: 30
}, {
text: 'Show all', value: allQuotes
}]
};
... and then somewhere later in your code where you want to display the table with pagination you just insert this:
<BootstrapTable
keyField='rowNumber'
data={ yourJsonData }
columns={ columns }
pagination={ paginationFactory(options) } />
I hope this solves your problem.
I've simplified your filtering logic and added client side pagination. Check out this simple working example (i've set item per page to 3, you can add more data and change it to 15 const QUOTES_PER_PAGE = <number of quotes per page>;)
const QUOTES_PER_PAGE = 3;
const Quote = ({text}) => <li>{text}</li>;
const Pagination = ({pages, goTo}) => (
<div>
{pages.map((p, i) => (
<button key={i} onClick={goTo} value={i}>{i+1}</button>
))}
</div>
)
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
page: 0,
pagedQuoutes: this.divideQuoutesIntoPages(props.quotes)
};
}
divideQuoutesIntoPages = (quotes => {
const pagedQuotes = [];
[...Array(Math.ceil(quotes.length/QUOTES_PER_PAGE))].forEach((q, i) => {
pagedQuotes.push(quotes.slice(0 + QUOTES_PER_PAGE*i, QUOTES_PER_PAGE + QUOTES_PER_PAGE*i))
})
return pagedQuotes;
})
filterQuoutes = (evt) => {
const filterValue = evt.target.value;
const filteredQuoutes = this.props.quotes.filter(q => !filterValue || q.theme === filterValue);
this.setState({
pagedQuoutes: this.divideQuoutesIntoPages(filteredQuoutes)
})
}
goToPage = (evt) => {
this.setState({
page: evt.target.value
})
}
render() {
return (
<div>
<h1>Quotes</h1>
<div>
Filters:
<button onClick={this.filterQuoutes}>All Quotes</button>
<button onClick={this.filterQuoutes} value="games">Games</button>
<button onClick={this.filterQuoutes} value="movies">Movies</button>
</div>
{this.state.pagedQuoutes[this.state.page]
.map(q => (
<ul>
<Quote {...q} />
</ul>
))}
<Pagination pages={this.state.pagedQuoutes} goTo={this.goToPage} />
</div>
);
}
}
const exampleQuotes = [{
theme: 'games',
text: 'games q1'
}, {
theme: 'games',
text: 'games q2'
}, {
theme: 'games',
text: 'games q3'
}, {
theme: 'games',
text: 'games q4'
}, {
theme: 'games',
text: 'games q5'
}, {
theme: 'movies',
text: 'movies q1'
}, {
theme: 'movies',
text: 'movies q2'
}, {
theme: 'movies',
text: 'movies q3'
}]
ReactDOM.render(<App quotes={exampleQuotes} />, document.getElementById("el"))
<div id="el"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Categories