I am working on a project using the Poke API.
My problem is when trying to filter the data from a search input,
I get the error: Cannot read properties of undefined (reading 'map')
When someone types in the search bar, I'm using filter to return a new array that matches either a name, id number, or type. I can see that a new array is returned, but the program breaks when trying to display the filtered data on the page.
It looks like the error occurs because the filter cannot iterate over a promise used to map specific data from the original array. But I'm not sure how to move forward.
I'm grateful to anyone who can point me in the right direction. Thanks for your help.
JS
const searchBar = document.getElementById('searchBar');
const pokemonData = [];
const getPokemonData = async () => {
for (let i = 1; i <= 151; i++) {
const url = `https://pokeapi.co/api/v2/pokemon/${i}`;
const res = await fetch(url);
const data = await res.json();
pokemonData.push(data);
}
Promise.all(pokemonData).then( (results) => {
const pokemon = results.map( (data) => ({
name: data.name,
id: data.id,
image: data.sprites['front_shiny'],
type: data.types.map((type) => type.type.name)
}));
//console.log(pokemon);
displayPokemon(pokemon);
});
}
const displayPokemon = (pokemon) => {
//console.log(pokemon);
const pokeDexContainer = document.querySelector('.pokedex');
const generateHtml = (pokemon).map( (mon) => {
return `
<li class="poke-card">
<image class="poke-image" src="${mon.image}" alt="${mon.name}"/>
${
( ids => {
if (ids < 10) {
return `<h2 class="poke-id">00${ids}</h2>`
}
if (ids >= 10 && ids < 100) {
return `<h2 class="poke-id">0${ids}</h2>`
}
if (ids >= 100) {
return `<h2 class="poke-id">${ids}</h2>`
}
})(mon.id)
}
<h1 class="poke-name">${mon.name}</h1>
//error message points here
//Cannot read properties of undefined (reading 'map')
${mon.type.map( (types) => {
return `<span class="poke-type ${types}">${types}</span>`
} ).join('')}
</li>
`
}).join('');
pokeDexContainer.innerHTML = generateHtml;
}
getPokemonData();
searchBar.addEventListener('keyup', (event) => {
//console.log(event.target.value);
const searchString = event.target.value.toLowerCase();
const searchNumber = event.target.value;
const filteredPokemon = pokemonData.filter( (mon) => {
return (
mon.name.toLowerCase().includes(searchString) ||
mon.id == searchNumber ||
mon.type == searchString
);
});
console.log(filteredPokemon);
displayPokemon(filteredPokemon);
});
HTML
<!-- search bar -->
<div id="search">
<input type="text" name="searchBar" id="searchBar" placeholder="search pokedex"/>
</div>
<!-- pokemon list container -->
<ul class="pokedex"></ul>
I figured it out, in case this may be helpful to someone else.
The displayPokemon function would break when passing in filteredPokemon. filteredPokemon was iterating over the pokemonData array which holds the raw data from the Poke API.
Instead filteredPokemon needs to iterate over the pokemon map within the getPokemonData function, which is how displayPokemon displays the correct data.
So, we push the pokemon map to a new array using the spread operator and iterate over new array in filteredPokemon.
let pokemonResults = [];
const getPokemonData = async () => {
let pokemonData = [];
for (let i = 1; i <= 151; i++) {
const url = `https://pokeapi.co/api/v2/pokemon/${i}`;
const res = await fetch(url);
const data = await res.json();
pokemonData.push(data);
}
Promise.all(pokemonData).then( (results) => {
const pokemon = results.map( (data) => ({
name: data.name,
id: data.id,
image: data.sprites['front_shiny'],
type: data.types.map((type) => type.type.name)
}));
pokemonResults.push(...pokemon);
displayPokemon(pokemon);
});
}
Note: Since pokemon types returns an array, it needs to be converted to a string in order to get the correct search results.
searchBar.addEventListener('keyup', (event) => {
const searchString = event.target.value.toLowerCase();
const searchNumber = event.target.value;
const filteredPokemon = pokemonResults.filter( (mon) => {
return (
mon.name.toLowerCase().includes(searchString) ||
mon.id == searchNumber ||
JSON.stringify(mon.type).toLowerCase().includes(searchString)
);
});
displayPokemon(filteredPokemon);
});
Related
i'm new on react hooks(typescript), here i'm having trouble figuring out how to filter by two, at the moment search filtering is working with one filter and it is 'receiver?.name?'
i want to add one more filter and it is 'sending?name?'
here is working code:
const handleSearchh = (searchText: string) => {
const filteredEvents = orders?.filter(({ receiver }) => {
const name = receiver?.name?.toLowerCase() ?? "";
return name.includes(searchText);
});
setData(filteredEvents);
};
<Searchh onSearch={handleSearchh} />
<Table
dataSource={Data}
columns={columns}
/>
i want to add this to it
const filteredEvents = orders?.filter(({ sending }) => {
const name = sending?.name?.toLowerCase() ?? "";
return name.includes(searchText);
});
it is antd table i want it to filter the search by two parameters , receivers and senders name
You can try this solution.
const handleSearchh = (searchText: string) => {
filterData(searchText)
};
const filterData = (searchText) => {
const filteredEvents = orders?.filter(({ receiver, sending }) => {
const receiverName = receiver?.name?.toLowerCase() ?? "";
const sendingName = sending?.name?.toLowerCase() ?? "";
return receiverName.includes(searchText) && sendingName.includes(searchText);
});
setData(filteredEvents);
}
This isn't antd specific, but can't you just chain the filter functions?
// ...
const filterFuncOne = ({ receiver }) => {
const name = receiver?.name?.toLowerCase() ?? "";
return name.includes(searchText);
};
const filterFuncTwo = ({ sending }) => {
const name = sending?.name?.toLowerCase() ?? "";
return name.includes(searchText);
}
const filteredEvents = orders?
.filter.(filterFuncOne)
.filter(filterFuncTwo); // <-- chained
//...
In the line setVotedPosts([...previousVotedPosts, postId]);
I'm trying to get the previous value of votedPosts, but I'm getting back the newest value.
full code : https://github.com/silvertechguy/reddit-clone/blob/main/src/components/vote-buttons.js
App live : https://reddit-clone-official.vercel.app/
const VoteButtons = ({ post }) => {
const [isVoting, setVoting] = useState(false);
const [votedPosts, setVotedPosts] = useState([]);
useEffect(() => {
const votesFromLocalStorage =
JSON.parse(localStorage.getItem("votes")) || [];
setVotedPosts(votesFromLocalStorage);
}, []);
const handleDisablingOfVoting = (postId) => {
const previousVotedPosts = votedPosts;
setVotedPosts([...previousVotedPosts, postId]);
localStorage.setItem(
"votes",
JSON.stringify([...previousVotedPosts, postId])
);
};
const handleClick = async (type) => {
setVoting(true);
// Do calculation to save the vote.
let upVotesCount = post.upVotesCount;
let downVotesCount = post.downVotesCount;
const date = new Date();
if (type === "upvote") {
upVotesCount = upVotesCount + 1;
} else {
downVotesCount = downVotesCount + 1;
}
await db.collection("posts").doc(post.id).set({
title: post.title,
upVotesCount,
downVotesCount,
createdAt: post.createdAt,
updatedAt: date.toUTCString(),
});
// Disable the voting button once the voting is successful.
handleDisablingOfVoting(post.id);
setVoting(false);
};
const checkIfPostIsAlreadyVoted = () => votedPosts.includes(post.id);
Problem
const previousVotedPosts = votedPosts;
In JavaScript, arrays are reference types, so you can't just create a new copy of an array using =.
Try this solution
Clone array using spread syntax(...).
const handleDisablingOfVoting = (postId) => {
const previousVotedPosts = [...votedPosts];
setVotedPosts([...previousVotedPosts, postId]);
localStorage.setItem(
"votes",
JSON.stringify([...previousVotedPosts, postId])
);
};
const getDate = (id) =>{
const userId = info.find(user => user.id === id)
return userId.date;
}
const getValueAtOne = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value[0]){
return userId?.value[0]
}
}
const getValueAtTwo = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value[1]){
return userId?.value[1]
}
}
const getAllValues = (id) => {
const userId = info.find(user => user.id === id)
if(userId?.value) {
const arrValue = userId?.roles.map((validData) => {
return { value: validData, label:validData };
});
return arrValue;
}
}
I have these 4 methods, which I am calling from different places in my code.But i want to optimize my code and want all these methods in a single method, but I cant figure out the best way to do it. first method returns me the date, second one returns the value of array at position 1, third method returns value of array at position 2, and 4th method returns the all the value and convert it in object.
You can return a single object like so:
const getAll = (id) => {
const userId = info.find((user) => user.id === id);
const [valueAtOne, valueAtTwo] = userId?.value ?? [];
const allValues =
userId?.value.roles.map((validData) => ({
value: validData,
label: validData,
})) ?? [];
return {
allValues,
valueAtOne,
valueAtTwo,
};
};
I'm running a Parse Query on my User class.
I want to retrieve users that are contained in an array of strings (ID).
const usersQuery = new Parse.Query(User).containedIn('objectId', customersArrayId).find({ useMasterKey: true });
Which works, actually. I'm getting a [ParseUser { _objCount: 6, className: '_User', id: 'iYIJ7Zrmms' }], because only 1 user matches.
But well, my issue is that I'm only getting ParseUser { _objCount: 6, className: '_User', id: 'iYIJ7Zrmms' }. This class contains other fields (firstname, lastname, e.g.) that are not returned.
When I performed the same thing, looping on my customersArrayId and performing .get():
const customerId = customersArrayId[index];
promises.push(new Parse.Query(User).get(customerId).then((user) => {
return user.toJSON();
}, (error) => console.error(error)));
I'm getting the full object, as expected. But it doesn't seem to be the right way of querying Parse objects from an array of ids.
I can't find anything in the docs about it, any idea why containedIn only returns a part of the queried objects?
I actually get the difference:
new Parse.Query(User).get(customerId)
=> returns the Parse Object
new Parse.Query(User).containedIn('objectId', customersArrayId)
=> returns the Parse User, subclass of a Parse Object.
And well, then, this thread was useful: Get user from parse in Javascript
I ended up using :
usersQuery.then(customersResponse => {
const customers = [];
for (let index = 0; index < customersResponse.length; index++) {
const customer = {
...customersResponse[index].toJSON(),
...customersResponse[index].attributes
};
...
Still not sure that the best answer.
EDIT
router.get('/:userId/shop/customers/details', checkUserMatch, (req, res, next) => {
if('shops' in req.jwtData.data) {
const shopId = req.jwtData.data.shops[0];
const query = new Parse.Query('OtherStuff');
const Shop = Parse.Object.extend('Shops');
query.equalTo('shop', new Shop({id: shopId})).find().then((otherStuffs) => {
const customersArrayId = otherStuffs.map(otherStuff => otherStuff.toJSON().user.objectId);
const usersQuery = new Parse.Query('_User').containedIn('objectId', customersArrayId).find({ useMasterKey: true });
usersQuery.then(customersResponse => {
const customers = [];
for (let index = 0; index < customersResponse.length; index++) {
let customer = {
...customersResponse[index].toJSON(),
...customersResponse[index].attributes
};
const customerId = customer.objectId;
const stuffQuery = new Parse.Query('Stuff').equalTo('user', new UserModel({objectId: customerId})).find().then((stuff) => {
return stuff;
});
const otherStuffQuery = new Parse.Query('otherStuff').equalTo('user', new UserModel({objectId: customerId})).find().then((otherStuff) => {
return otherStuff;
});
Promise.all([stuffQuery, otherStuffQuery]).then((data) => {
const stuff = data[0];
const otherStuff = data[1];
customer = {
...customer,
stuff,
otherStuff,
}
customers.push(customer);
if(index === customersResponse.length - 1) { // last customer
res.json({
success: true,
data: customers
});
}
})
}
});
});
}
});
I have an API call in api.js:
export const getGraphData = (domain, userId, testId) => {
return axios({
url: `${domain}/api/${c.embedConfig.apiVersion}/member/${userId}/utests/${testId}`,
method: 'get',
});
};
I have a React helper that takes that data and transforms it.
import { getGraphData } from './api';
const dataObj = (domain, userId, testId) => {
const steps = getGraphData(domain, userId, testId)
.then((result) => {
return result.attributes;
});
console.log(steps);
// const steps = test.get('steps');
const expr = /select/;
// build array of steps that we have results in
const resultsSteps = [];
steps.forEach((step) => {
// check for types that contain 'select', and add them to array
if (expr.test(step.get('type'))) {
resultsSteps.push(step);
}
});
const newResultsSteps = [];
resultsSteps.forEach((item, i) => {
const newMapStep = new Map();
const itemDescription = item.get('description');
const itemId = item.get('id');
const itemOptions = item.get('options');
const itemAnswers = item.get('userAnswers');
const newOptArray = [];
itemOptions.forEach((element) => {
const optionsMap = new Map();
let elemName = element.get('value');
if (!element.get('value')) { elemName = element.get('caption'); }
const elemPosition = element.get('position');
const elemCount = element.get('count');
optionsMap.name = elemName;
optionsMap.position = elemPosition;
optionsMap.value = elemCount;
newOptArray.push(optionsMap);
});
newMapStep.chartType = 'horizontalBar';
newMapStep.description = itemDescription;
newMapStep.featured = 'false';
newMapStep.detailUrl = '';
newMapStep.featuredStepIndex = i + 1;
newMapStep.id = itemId;
newMapStep.isValid = 'false';
newMapStep.type = 'results';
const listForNewOptArray = List(newOptArray);
newMapStep.data = listForNewOptArray;
newMapStep.userAnswers = itemAnswers;
newResultsSteps.push(newMapStep);
});
return newResultsSteps;
};
export default dataObj;
The issue is steps, when logged outside the .then() returns a Promise {<pending>}. If I log results.attributes inside the .then(), I see the data fully returned.
You need to wait until your async call is resolved. You can do this by chaining on another then:
getGraphData(domain, userId, testId)
.then((result) => {
return result.attributes;
})
.then(steps => {
// put the rest of your method here
});
You can also look at async/await if your platform supports it which would allow code closer to your original
const steps = await getGraphData(domain, userId, testId)
.then((result) => {
return result.attributes;
});
// can use steps here
You have 2 options to transform your fetched data :
1st option : create a async function that returns a promise with the modified data :
const dataObj = (domain, userId, testId) => {
return getGraphData(domain, userId, testId).then((result) => {
const steps = result.attributes;
const expr = /select/;
// build array of steps that we have results in
const resultsSteps = [];
steps.forEach((step) => {
// check for types that contain 'select', and add them to array
if (expr.test(step.get('type'))) {
resultsSteps.push(step);
}
});
const newResultsSteps = [];
resultsSteps.forEach((item, i) => {
const newMapStep = new Map();
const itemDescription = item.get('description');
const itemId = item.get('id');
const itemOptions = item.get('options');
const itemAnswers = item.get('userAnswers');
const newOptArray = [];
itemOptions.forEach((element) => {
const optionsMap = new Map();
let elemName = element.get('value');
if (!element.get('value')) {
elemName = element.get('caption');
}
const elemPosition = element.get('position');
const elemCount = element.get('count');
optionsMap.name = elemName;
optionsMap.position = elemPosition;
optionsMap.value = elemCount;
newOptArray.push(optionsMap);
});
newMapStep.chartType = 'horizontalBar';
newMapStep.description = itemDescription;
newMapStep.featured = 'false';
newMapStep.detailUrl = '';
newMapStep.featuredStepIndex = i + 1;
newMapStep.id = itemId;
newMapStep.isValid = 'false';
newMapStep.type = 'results';
const listForNewOptArray = List(newOptArray);
newMapStep.data = listForNewOptArray;
newMapStep.userAnswers = itemAnswers;
newResultsSteps.push(newMapStep);
});
return newResultsSteps;
});
};
With es7 async/await syntax it should be :
const dataObj = async (domain, userId, testId) => {
const result = await getGraphData(domain, userId, testId);
const steps = result.attributes;
... modify the data
}
Then keep in mind that this function returns a promise, you'll need to wait for it to get the result, example in a react component :
componentDidMount(){
dataObj('mydomain', 'myuserId', 'mytestId').then((res) => {
this.setState({ data: res });
}
}
The component will update when the promise is resolve, you can then use the data (you'll need to handle the undefined data state in render method)
2nd option : Create a sync function to modify the data :
const dataObj = (steps) => {
const expr = /select/;
const resultsSteps = [];
steps.forEach((step) => {
...
}
return newResultsSteps;
};
To have the same result as option 1 in our component we'll use it like this :
componentDidMount(){
getGraphData('mydomain', 'myuserId', 'mytestId').then((res) => {
const modifiedData = dataObj(res);
this.setState({ data: modifiedData });
}
}
That's how promises work. The data is not ready when you are trying to use it so you should move all your processing into the .then. The reason your variable is a Promise {<pending>} is because you can chain other things onto it.
Something like:
steps.then((steps) => {
...
});