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
//...
Related
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);
});
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,
};
};
Here I validate if my users status is true, and if they are, I put them in an array. The thing here is that next time it will validate, all those who already was true will be added to the same array. Can it be solved by filter instead of push, or should I take the validation in any other way?
import {
UPDATE_LIST_SUCCESS
} from './types'
var arr = []
export const fetchList = () => {
return (dispatch) => {
firebaseRef.database().ref().child('users')
.on('value', snapshot => {
snapshot.forEach(function (child) {
var data = child.val()
if (child.val().profile.status === true) {
arr.push(data)
}
})
dispatch({ type: UPDATE_LIST_SUCCESS, payload: arr })
})
}
}
You can do it like this:
import {
UPDATE_LIST_SUCCESS
} from './types'
export const fetchList = () => {
return (dispatch) => {
firebaseRef.database().ref().child('users')
.on('value', snapshot => {
var arr = snapshot.filter(function (child) {
return child.val().profile.status === true
}).map(function (child) {
return child.val();
});
dispatch({ type: UPDATE_LIST_SUCCESS, payload: arr })
})
}
}
So here is my not so pretty way of solving it, but it works.
import {firebaseRef} from '../firebase/firebase'
import {
UPDATE_LIST_SUCCESS
} from './types'
export const fetchList = () => {
return (dispatch) => {
const arrayToFilter = []
firebaseRef.database().ref().child('users')
.on('value', snapshot => {
let snap = snapshot.val()
// Get acces to the keys in the object i got from firebase
let keys = Object.keys(snap)
// iterate the keys and put them in an User object
for (var i = 0; i < keys.length; i++) {
let k = keys[i]
let name = snap[k].profile.name
let age = snap[k].profile.age
let status = snap[k].profile.status
let profile_picture = snap[k].profile.profile_picture
let users = {name: '', age: '', status: Boolean, profile_picture: ''}
users.name = name
users.age = age
users.status = status
users.profile_picture = profile_picture
// adding the user object to an array
arrayToFilter.push(users)
}
// filter and creates a new array with users depending if their status is true
let arr = arrayToFilter.filter(child => child.status === true)
dispatch({ type: UPDATE_LIST_SUCCESS, payload: arr })
})
}
}
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) => {
...
});