Problem occur when create a search function in React - reactjs

I'm new to react, and I'm working on a small project that uses a search bar to find data that I've gotten from my database.
The code that I tried is below:
function Posts() {
const [notes, setNotes] = useState([]);
useEffect(()=>{
getAllNotes();
}, []);
const getAllNotes = async () => {
await axios.get(`/buyerPosts`)
.then ((response)=>{
const allNotes=response.data.existingPosts;
setNotes(allNotes);
})
.catch(error=>console.error(`Error: ${error}`));
}
console.log(notes);
const filterData = (postsPara, searchKey) => {
const result = postsPara.filter(
(notes) =>
notes?.address.toLowerCase().includes(searchKey) ||
notes?.contact.toString().toLowerCase().includes(searchKey)
);
setNotes(result);
};
const handleSearchArea = (e) => {
const searchKey = e.currentTarget.value;
axios.get(`/buyerPosts`).then((res) => {
if (res?.data?.success) {
filterData(res?.data?.existingPosts, searchKey);
}
});
};
return(
<div className="posts-b">
<div className="posts__container-b">
<div className="search-box">
<input type="text" placeholder="What are you looking for?" onChange={handleSearchArea}></input>
<i className="fas fa-search"></i>
</div>
<main className="grid-b">
{notes.map((note,index)=> (
<article>
<div className="text-b">
<h3>Post ID: {index + 1}</h3>
<p>Location: {note.address}</p>
<p>Post Type: {note.postType}</p>
<p>Address: {note.address}</p>
<p>Telephone No: {note.contact}</p>
</div>
</article>
))}
</main>
</div>
</div>
);
}
export default Posts;
From the first API call, I get a length 10 array of objects. This image shows the data that I got from the first API call.
There is another array of objects called as wasteItemList in all 10 array as in this picture. I created the search function correctly and it works to search the data in the above length 10 array of objects using this code notes?.address.toLowerCase().includes(searchKey) || notes?.contact.toString().toLowerCase().includes(searchKey). Then I try to modify above code to search the data inside the wasteItemList array like this notes?.wasteItemList?.item.toLowerCase().includes(searchKey) || notes?.wasteItemList?.wasteType.toLowerCase().includes(searchKey). But it does not work and get an error that says 'Unhandled Rejection (TypeError): Cannot read property 'toLowerCase' of undefined'.
What is the reason for this problem. Is this impossible to search the data in an inside array of objects that are already in another array of objects? If possible how can I solve this problem?
Any other comments on the code are also welcome. I'm here to learn.
Thank you!

notes?.address is single string attribute however, notes?.wasteItemList is list of objects. hence notes?.wasteItemList?.item will return an undefined
what you can do is run a map to extract a list of item key and join using join function and then use includes function,
the following snippets will get you the idea
notes?.wasteItemList?.map(wasteItem => wasteItem.item).join(' ').toLowerCase().includes(searchKey)

wasteItemList is an array, you are trying to access it with:
wasteItemList?.item.toLowerCase()
AND
wasteItemList?.wasteType.toLowerCase()
This will cause wasteType to be undefined, so toLowerCase() will also throw an error since it will be similar to that you are executing this:
undefined.toLowerCase() => Cannot read property 'toLowerCase' of undefined
For wasteItemList is an array so if you want to access its variable need to access it with another loop.

You're trying to call .toLowerCase() on an array. You will need to search within that array (it doesn't matter that it's nested inside an object in another array) - here's a suggestion for how to do that:
const result = postsPara.filter(notes =>
notes?.wasteItemList?.some(
wasteItem =>
wasteItem.item?.toLowerCase().includes(searchKey) ||
wasteItem.wasteType?.toLowerCase().includes(searchKey),
),
);
Since this is a general javascript question and not specific to react, you might want to change the tag.

Related

React.js warning when iterating a list with map()

I got this warning from web debug console:
react-jsx-dev-runtime.development.js:87 Warning: Each child in a list should have a unique "key" prop.
Check the render method of App. See https://reactjs.org/link/warning-keys for more information. at div at App (http://localhost:3000/static/js/bundle.js:31:80)
Below is my code:
import './App.css';
import {ethers} from "ethers";
import { useState } from 'react';
function App() {
const [account, setAccount] = useState("")
const [data, setData] = useState([])
console.log(data);
const connect = async () => {
const provider = new ethers.providers.Web3Provider(window.ethereum)
let res = await provider.send("eth_requestAccounts", []);
setAccount(res[0]);
getData(res[0]);
}
const getData = (_account) => {
const options = {method: 'GET', headers: {accept: 'application/json'}};
fetch(
'https://api.opensea.io/api/v1/collections?asset_owner=0x3FB65FEEAB83bf60B0D1FfBC4217d2D97a35C8D4&offset=0&limit=3',
// `https://api.opensea.io/api/v1/collections?asset_owner=${_account}&offset=0&limit=3`,
options)
.then(response => response.json())
.then(response => {
setData(response);
console.log(response)})
.catch(err => console.error(err));
};
return (
<div className="App">
<button
onClick={connect}>
Connect
</button>
{data.map(nft => {
return(<div>
<img src={nft.featured_image_url} width="100px" height="100px"/>
<p>
{nft.discord_url}
</p>
<p>
{nft.banner_image_url}
</p>
</div>)
})}
<button
onClick={getData}>
GetData
</button>
</div>
);
}
export default App;
The code actually works as I expected. but when opening debug console from chrome I can see this warning pasted above.
Not sure why this warning? need some help, thank you
Googled this warning msg but cannot find useful info for this warning.
Is this warning a real issue or this can be ignored?
You need to add a key to your returned element, because React need to differentiate each elements.
You just need to add the parameter key to your block like:
{data.map(nft => (
<div key={nft.id}>
<img src={nft.featured_image_url} width="100px" height="100px"/>
<p>
{nft.discord_url}
</p>
<p>
{nft.banner_image_url}
</p>
</div>
))}
Why did I used nft.id ?
Most often, peoples use array map()'s index property as key, but it can be a bad practice depending on your goal.
using:
{data.map((nft, index) => (
<div key={index}>
...
))}
Works pretty fine, BUT in some cases (not that rare), when you perform edit action on your array's element, and you end up sorting them, React will be very confused.
Imagine you create an online music platform such as Spotify, and your API return you a list of your own playlist, ordered by name. The day you'll edit one playlist name, your entire playlist will have unwanted comportement because your element array's order will be modified, and the index you used as key.
So you may use map's index property as key, but be aware of what you need, it's generally better to use your element's id, uuid or other unique value as key.
See more on the official website
You must provide a unique key when you map with react.
Here is how :
{data.map((nft, index) => {
return(<div key={index}>
This is just an example. You can provide your own index.div key={nft} could work too if it is unique for each data.

Unable to loop single react component

I'm trying to loop my StudentInfo component so a blurb of information can be repeated down an array of 25 objects provided through an API for each object/a student (name, email, company, etc.) I'm not
sure where I'm going wrong; here is my attempted loop via the map function:
export default function StudentList() {
let students = useState(null);
return (
<div className="StudentList">
<div className="row">
<div className="col">
{students.map(function (student, index) {
if (index <= 25) {
return (
<div className="col" key={index}>
<StudentInfo data={student} />
</div>
);
}
})}
</div>
</div>
</div>
);
}
Can someone see what I'm missing or what I might have skipped? I usually assume that I must be doing something wrong because I'm still new at React and trying to adapt other code I used for a weather forecast app to be used for this, but I don't think it's translating over well.
When I run this, I see the first object twice, i.e. Name, Email, Company, etc. it shows the info for the same person twice, rather than the first person, the second person, etc. I want to be able to see this pull all the objects from the array.
Here is the information I'm pulling to return as an entry on student info:
export default function StudentInfo() {
const [info, addInfo] = useState(" ");
function setInfo(response) {
addInfo({
number: response.data.students[0].id,
first: response.data.students[0].firstName,
last: response.data.students[0].lastName,
email: response.data.students[0].email,
company: response.data.students[0].company,
skill: response.data.students[0].skill,
average: response.data.students[0].grades[0],
});
}
let url = "https://api.hatchways.io/assessment/students";
axios.get(url).then(setInfo);
return (
<div className="StudentInfo">
<h1>{info.number}.</h1>
<h2>
Name: {info.first} {info.last}
</h2>
<h2>Email: {info.email}</h2>
<h2>Company: {info.company}</h2>
<h2>Skill: {info.skill}</h2>
<h2>Average: {info.average}</h2>
</div>
);
}
I'm using "if (index <= 25)" as there are 25 total entries that I want showing, but as I mentioned, I have no doubt I'm going about this incorrectly. I want this to loop through all 25 objects with the above information, as I keep saying. I'm sorry if I'm not speaking technically enough to be understood, as I am still learning.
I just want this to return 25 times with info from each object so that it's all listed out.
This is what it currently looks like
UPDATE
I've been tinkering and was able to repeat the entry, however I'm now having trouble getting the unique information, i.e. I'm only seeing the first entry over and over. I'm not sure how to reiterate with new information since it's only going to response.data.students[0]. This is what my code looks like now, where the mapping is:
export default function StudentList() {
let [loaded, setLoaded] = useState(false);
let [students, setStudent] = useState(" ");
function doInfo(response) {
console.log(response.data);
setStudent(response.data.students[0].id);
setLoaded(true);
}
if (loaded) {
return (
<div className="StudentList">
<div className="row">
<div className="col">
{students.map(function (id) {
return <StudentInfo data={id} />;
})}
</div>
</div>
</div>
);
} else {
let url = "https://api.hatchways.io/assessment/students";
axios.get(url).then(doInfo);
}
}
Can someone help me code it so it runs through all 25 entries, not just the first one?
There are some errors in your code.
UseState
React useState hook returns an array with a value and a setter method to update the state useState docs.
const [students, setStudents] = useState(null);
Iterate over null values
If your state starts with a null value you will not be able to iterate over it. To avoid getting an error you should make sure to use the map operator when your state has a value.
{students && students.map(function (student, index) {
...
})}
Handling side effects
You should move your API request (and set your info state) inside of a useEffect (useEffect docs). This way you will set those values asynchronously after the component is mounted.
export default function StudentInfo() {
const [info, addInfo] = useState(null);
useEffect(() => {
async function fetchInfo() {
const url = "https://api.hatchways.io/assessment/students"; //should be a constant outside the component
const response = await axios.get(url);
addInfo({
number: response.data.students[0].id,
first: response.data.students[0].firstName,
last: response.data.students[0].lastName,
email: response.data.students[0].email,
company: response.data.students[0].company,
skill: response.data.students[0].skill,
average: response.data.students[0].grades[0],
});
fetchInfo();
}, [])
return (
<div className="StudentInfo">
{info &&
<h1>{info.number}.</h1>
<h2>
Name: {info.first} {info.last}
</h2>
<h2>Email: {info.email}</h2>
<h2>Company: {info.company}</h2>
<h2>Skill: {info.skill}</h2>
<h2>Average: {info.average}</h2>
}
</div>
);
}
export default function StudentList() {
const [students, setStudents] = useState([]);
Check React's documentation React.
Also check if it cannot be declared that way, you need to use a ternary if. Check that part in React's documenation Conditional rending
{students.map(function (student, index) {
index <= 25 && (
<div className="col" key={index}>
<StudentInfo data={student} />
</div>
);

Why do i keep getting typeerror: not a function while trying to filter or map an array

So, I am trying to filter and map an array from the GIPHY api using stored variables in the useState hook.
Here's my code
const [gifdata, setGifdata] = useState([])
const [Search, setSearch] = useState("")
function handleChange(e) {
setSearch(e.target.value)
}
useEffect(()=> {
axios.get(`https://api.giphy.com/v1/gifs/trending?api_key=nKEFKPSILLeIlqLEjqhVsRO8ShxIjfcn&limit=50&rating=g`)
.then(res=>{
setGifdata(res.data)
console.log(res.data)
})
}, [])
const filteringSearch = gifdata.filter(gif=>
gif.title.toLowerCase().includes(Search.toLowerCase()))
return (
<div>
<header className="bg-blue-600">
<div className="logo">
<label htmlFor="logo">DejareX</label>
</div>
</header>
<div className="heroSection mx-auto">
<h1>GIF Collections at it's peak</h1>
<p>loremipsum blah blah blah</p>
<input type="text" placeholder="Search For A GIF" onChange = {handleChange} />
{filteringSearch.map(gif => {
return (
<Gif
key = {gif.id}
gifImgSrc = {gif.images.original.webp}
description = {gif.title}
/>
)
})}
</div>
</div>
)
}
NOTE: CREATED A RANDOMEMAIL ADDRESS TO GET THIS API KEY, Its not for production.
I am new to react, please try put me through as easy as possible
Someone said the data from the api is probably not an array, i rechecked and it seems like it is true. Cause it first returns an object before getting into the array, who can help with fixing that please
As I said, res.data is not an array. axios adds another data layer to the result. Therefore your res.data is not the same as you see in the browser, in fact it is:
{data: Array(50), pagination: Object, meta: Object}
Therefore, changing res.data to res.data.data will solve the issue
Here is a dummy Live Demo

Single responsibility in React component

I was learning Single responsibility principle in React and created such component:
import React from "react";
import {useGetRemoteData} from "./useGetRemoteData";
export const SingleResponsibilityPrinciple = () => {
const {filteredUsers , isLoading} = useGetRemoteData()
const showDetails = (userId) => {
const user = filteredUsers.find(user => user.id===userId);
alert(user.contact)
}
return <>
<div> Users List</div>
<div> Loading state: {isLoading? 'Loading': 'Success'}</div>
{filteredUsers.map(user => {
return <div key={user.id} onClick={() => showDetails(user.id)}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
})}
</>
}
As you can see above, I have this code
const user = filteredUsers.find(user => user.id===userId);
The question is Is it best practice that if whenever we use map, reduce or any array function in React component, we should extract that logic out of a component, that is, filteredUsers.find(user => user.id===userId); should be extracted and we need to create utility function. So, function should not care about how a particular thing is done. Is it true?
Thank you for your question. I want to advice as follows
It is better for your to check if filteredUsers exists or not in your return.
{filteredUsers?.map(user=>{
//your code
})
You don't need to get specific user as you already loop in your map method. Just simply do something like this
{filteredUsers.map(user => {
return <div key={user.id} onClick={() => showDetails(alert(user.contact))}>
<div>{user.name}</div>
<div>{user.email}</div>
</div>
})}
Remember the difference between find, filter method of Javascript array. If you have unique value according to userId, you simply use find method to get the first value not array, if you use filter, you get arrays of the condition. Are you sure you don't need to alert(user[0].contact), not alert(user.contact)?

Attempting to map within React returning undefined

I have the following Mongoose schema:
const SubmitDebtSchema = new Schema ({
firebaseId: String,
balance: [{
balanceDate: Date,
newBalance: Number
}]
});
This database schema is called in my parent component using the useEffect hook, and passed down as props to my child component.
const fetchDebts = debts.map (debt => {
return (
<IndividualDebtCard key={debt._id}
transactions={debt} />
)
})
I then store the prop in my child component as a variable, and use another useEffect to console the result of this variable upon rendering:
const debts = props.transactions
useEffect(() => {
console.log(debts)
}, [debts])
For reference, this is what an example console log would look like:
balance: Array (2)
0 {_id: "5fea07cd143fd50008ae1ab2", newBalance: 1500, balanceDate: "2020-12-28T16:29:00.391Z"}
1 {_id: "5fea0837b2a0530009f3886f", newBalance: 1115, balanceDate: "2020-12-28T16:30:45.217Z"}
What I then want to do, is map through this variable, pick out each 'newBalance', and 'balanceDate' and render them on my page.
However, I'm getting an undefined error every time I try to load my component...
This is what I've tried so far:
{ debts.map(debt => {
return (
<div className="transaction-history">
<div className="transaction-history-entry">
<p>{debt.balance.balanceDate}</p>
<p>-£{debt.balance.newBalance}</p>
</div>
</div>
)
})}
Can anyone point out where I'm going wrong? I know it'll be something obvious, but can't figure it out.
EDIT: I think the undefined is coming from how I'm attempting to call my 'balanceDate' and 'newBalance' - if I console log what I'm trying to map it's returning undefined.
You need to check for debts to have value. try this:
{
debts.balance && !!debts.balance.length && debts.balance.map((debt, index) => {
return (
<div key={index} className="transaction-history">
<div className="transaction-history-entry">
<p>{debt.balance.balanceDate}</p>
<p>-£{debt.balance.newBalance}</p>
</div>
</div>
);
});
}

Resources