Cannot map value of object seen in console - javascript

My idea is to click on any of the buttons on the left navbar and whenever the name of the clicked button in the logos object matches any of the names in the items object within projects, then display those objects.
When I click on any of the buttons on the left, I transform that object's active property to true within the logos object. After I've filtered the values, I can see all of the correct values in the console, but I can't loop through them - with a for loop or a map. Oddly enough, when I write filteredValues[0] I am able to output that data to the screen but since I want multiple outputs for some of these clicked values, this is not an option. Any help is appreciated. Thank you!
These are the items that I can't loop through but am getting back when I console log them
These are my projects
These are my logos
const Homepage = () => {
const {state} = useContext(Context)
const {projects,logos} = state;
return (
<>
<div className="container">
<Language />
<div className="homepage">
{logos.map(logo => {
let filteredValues;
if(logo.active == true){
filteredValues = Object.values(projects).filter(({items}) => Object.values(items).includes(logo.name))
filteredValues.map((val) =>{
console.log(val)
return(
<div>{val.title}</div>
)
}) //end of filteredValues.map
} //end of if
}) // end of logos.map
}
</div>
</div>
</>
)}

An Array.map() would always return an Array of same length, irrespective of you return anything or not. If you do not return anything then
it would be a sparse Array with empty values.
Try returning only the array with the required data. Here I have just separated out the Logos logic in a separate variable.
render() {
const Logos = (
<div className="homepage">
logos.reduce((acc, logo) => {
if (logo.active) {
const filteredValues = Object.values(projects).filter(({items}) => Object.values(items).includes(logo.name));
Object.values(projects).forEach(({items}) => {
if (Object.values(items).includes(logo.name)) {
acc.push((<div>{val.title}</div>));
}
});
}
return acc
}, [])
</div>
)
return (
<div className="container">
<Language />
{Logos}
// rest of the code
)
}

Related

Custom function I made that returns <span> elements is getting "Warning: Each child in a list should have a unique 'key' prop"

I am trying to create a function that takes an array of words such as:
const words = ['hello', 'my', 'name']
and returns them in the form:
<>
<span>hello</span> // <span>my</span> // <span>name</span>
</>
This is the react component I created to do this:
function StyleWords({words, ...props}){
return(
words.map((word, index, words) => {
if(index != words.length-1){
return <><span key={word} className='highlight'>{word}</span> // </>
}
else{
return <span key={word} className='highlight'>{word}</span>
}
})
)
}
and then call it like so:
<div><StyledWords words={['hello', 'my', 'name']} /></div>
Now this does work but I get the warning about keys. So my thinking is that either I have done this inappropriately or that I have missed something out with the keys. Any help?
You need to provide the key to the component which is the root of the item in the list.
function StyleWords({words, ...props}){
return(
words.map((word, index, words) => {
if(index != words.length-1){
return <React.Fragment key={word}>
<span className='highlight'>{word}</span> //
</React.Fragment>
}
else{
return <span key={word} className='highlight'>{word}</span>
}
})
)
}
As Lokesh suggested, you should use a unique value as key for the items instead of using word if it is not guaranteed that it will be unique.

Remove element from useState array by index

SOLUTION: Update the key value for the input element to refresh the default value => content of the input element. Deleting an element from the array DID work. Thanks for your help!
src: https://thewebdev.info/2022/05/12/how-to-fix-react-input-defaultvalue-doesnt-update-with-state-with-javascript/#:~:text=state%20with%20JavaScript%3F-,To%20fix%20React%20input%20defaultValue%20doesn't%20update%20with%20state,default%20value%20of%20the%20input.
I got an useState array in my code which represents a lisst of students:
const [students, setStudents] = useState([""]);
This array gets mapped to student elements:
{students.map((student, index) => <Student setStudents={setStudents} students={students} id={index} key={index} content={student} />)} I also got an AddStudent element which adds students to the array.
function AddStudent(props) {
const {setStudents} = props;
return (
<button className="change-student add-student" onClick={() => {
setStudents((students) => [...students, ""])
}}>
+
</button>
);
}
The RemoveStudent component is supposed to remove a student by its index in the array. I've tried many different ways but none worked correctly. How can I get it to work? Here is my code:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button className="change-student remove-student" onClick={() => {
let data = students;
if(id > -1) {
data.splice(id, 1);
}
console.log(data)
// setStudents(data)
// alternative:
// setStudents(students.filter(index => index !== id)); // removes the last element in the list
// doesn't work properly
}}>
-
</button>
)
}
Thanks for your help!
2 things should be noted here:
While updating react state arrays, use methods that return a new array (map, filter, slice, concat),
rather than ones that modify the existing array (splice, push, pop, sort).
While updating React state using its previous value, the callback argument should be used for the state setter. Otherwise you may get stale values. (See React docs).
if(id > -1) {
setStudents(students=> students.filter((s,i)=>(i != id)))
}
Consult this article, for a complete reference about how to update React state arrays.
You need to copy the students array first and then try removing the student by index. I assume by id you mean index at which to remove the student. Then you can try something like:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button
className="change-student remove-student"
onClick={() => {
if(id > -1) {
const data = [...students]; // making a copy
data.splice(id, 1); // removing at index id
console.log(data)
setStudents(data)
}
}}
>
-
</button>
)
}
With array.filter() you have a mistake in how you pass callback to filter() method. Please try the following:
setStudents(students.filter((,index) => index !== id));
Notice the index is second param of the callback so I used a , before index.
After #Irfanullah Jan 's answer you should make sure how you show the student.
Here is the simple example:
const [students, setStudents] = useState([1, 2, 3]);
return (
<div>
{students.map((student, index) => {
return <div>{student}</div>; // show the value not the index
})}
<button
onClick={() => {
let id = 1;
const copy = [...students];
copy.splice(id, 1)
console.log(copy)
setStudents(copy);
}}
>
-
</button>
</div>
);
The code above will delete the student of "index==1"

Accessing nodes returns null in react

I am trying to render a list of searched users returned from server and add when clicked on any of the list items, get the value of inside the node component.
First I fetch the users
useEffect(() => {
if (searchText.length === 0) {
setSearchedUsers([])
return
}
fetch(--users--)
}).then((res) => res.json())
.then((users) => {
setSearchedUsers(users)
})
}, [searchText])
then render
const searchedNodes = useRef([])
function renderWithRefs() {
return (searchedUsers.map((user, i) => (
<div className={`searched-user-div ${i}`} ref={(div) => searchedNodes.current[i] = div} key={i} onClick={(e) => showUserData(e)}>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
)))
}
function showUserData(e) {
let ref = searchedNodes.current[parseInt(e.target.className.split(" ")[1])]
let Name = ref.querySelector(".searchedUser-Name").textContent
let age = ref.querySelector(".searchedUser-age").textContent.slice(1)
....
}
when clicked I get Error: cannot read property 'querySelector' of undefined
Directly querying the dom in React is a very bad pattern. You should not do it unless you have no choice ( for example you need to query the size of an item ).
In your case, you could do something way easier like this:
<div className={`searched-user-div ${i}`} key={i} onClick={(e) => showUserData(user)}>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
You then don't have to query the DOM.
First,
The use of array index as a key is not recommended
It's a bad idea to use the array index since it doesn't uniquely
identify your elements. In cases where the array is sorted or an
element is added to the beginning of the array, the index will be
changed even though the element representing that index may be the
same. This results in unnecessary renders.
function renderWithRefs() {
return (searchedUsers.map((user, i) => (
<div className={`searched-user-div`} ref={(div) => searchedNodes.current[i] = div} key={JSON.Stringlify(user)} onClick={() => showUserData(user)} onKeyPress={() => showUserData(user)} tabIndex={0} role="button">
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
</div>
)))
}
function showUserData(user) {
//here you can show all user data
<>
<p className="searchedUser-Name">{user.Name}</p>
<p className="searchedUser-age">{user.Age}</p>
<p className="searchedUser-LastName">{user.LastName}</p>
<p className="searchedUser-tel">{user.tel}</p>
....
</>
....
}
You can see I added a few props to your div all because of no-static-element-interactions
Static HTML elements do not have semantic meaning. This is clear in
the case of and . It is less so clear in the case of
elements that seem semantic, but that do not have a semantic mapping
in the accessibility layer. For example , , ,
, , and -- to name a few -- have no
semantic layer mapping. They are as void of meaning as .

How to search in a json array, and return the result as I type in the input

So far I can only search an element of the array if I type the exact name present in my api, in this my api has an array with 20 positions and if I type exactly the name of the element I search it returns an array with 19 positions with undefined and 1 position with the array found, what I want to do and search while I type instead of searching only when I type the full name.
After my search I try to change the state of a component so that it is rendered only with the value fetched, but this does not happen, if anyone knows I am very grateful.
updated code
import data from "../sample_data/recipes.json";
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchString: []
};
}
componentDidMount() {
this.setState({ searchString: data.results })
}
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var indexes = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) !== -1;
})
this.setState({ searchString : indexes });
}
render() {
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={this.state.searchString} />}
</div>
</div>
</div>
);
}
}
export default App;
I suppose that you want some kind of filtering while you're typing:
can you try this ? :
onChange(fieldName) {
if (fieldName === '' || fieldName === null) this.setState({ searchString: data.results });
var filteredItems = data.results.filter((item, i) => {
return item.title.toLowerCase().indexOf(fieldName.toLowerCase()) === -1;
})
this.setState({ searchString : filteredItems });
}
Explanation :
What is requested is to not display items that contains typed letters, to do that, you can use filter items with filter method and return only the items which doesn't have the typed letters in the title (using the indexOf method).
move setState outside map.
replace map with for loop
maybe try this?
var indexes = [];
data.results.forEach(item => {
if(item.title.indexOf(myObjTitle.title)) {
indexes.push(item);
}
});
this.setState({searchString : indexes});
As I understand your question, you are trying to search the element from json Array.I just added the new key to store filterString. Here is my solution
onChange(firstName){
if(firstName != undefined){
this.setState({
nameToFilter : firstName
})
}
}
//Inside the render method
I am using nameToFilter to filter the data.
render() {
let searchString = this.state.searchString
if(this.state.nameToFilter != undefined && this.state.nameToFilter.length>0)
{
searchString = this.state.searchString.filter(item => (
item.title == this.state.nameToFilter
));
}
return (
<div className="App">
<Navbar onChange={this.onChange.bind(this)} />
<div className="container mt-10">
<div className="row">
{<RecipeItem list={searchString} />}
</div>
</div>
</div>
);
}
It seems you are trying to filter the matching results. In which case it's probably better to use filter instead of map.
onChange(value) {
let results = [];
// Empty string is `falsy`, avoid filtering when no value was given
if (value) {
/*
Pick only one option, remove the other two, or you will
process your data three times instead of once!
*/
// Case-sensitive, exact match
results = data.filter(item => item.title === value);
// Case-insensitive, exact match, value and title are the same when lowercased
results = data.filter(item => (
item.title.toLowerCase() === value.toLowerCase()
));
// Case-insensitive, partial match, value was found *within* title
results = data.filter(item => (
item.title.toLowerCase().indexOf(
value.toLowerCase()
) !== -1
));
}
this.setState({results});
}
Additionally, if you want a single result (the first one that matches), using find instead of filter is better, because it would stop searching after the first match instead of traversing the whole array.
Map
Returns a new array with as many entries as you previously had, but you may return anything you want as the entries, this is why you get so many undefined entries right now. (Because you only return an item when the title matches, hence you implicitly return undefined when not). Map loops through the whole array.
Filter
Returns a new array with as many entries matches your test function. If the function returns true, then the item is included, it it returns false, the item will be omitted. Filter loops through the whole array.
Find
Will only retrieve the first entry that matches your test function. It will stop looping once a match is found.
Bonus
You will most likely need to learn about debouncing, which you can do with lodash.debounce.
Debouncing is a method used to prevent a function from executing many times in a short interval (so you don't unnecessarily re-render)

Retrieve clicked array item on React Component

I have a Search component that outputs values from an array to a ResultItem child component, every child component has a button with a onClick property on it. I bound a function to the button to get the value of an array item that I clicked.
What I have working is that every time I clicked on every single ResultItem button I get values of 0,1,2,3,4,5,6 individually which is perfect but I dont need the array indexes I need the values of those indexes
class ResultItem extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick
}
handleClick(index) {
// index = this.props.applications[0]
// index = this.props.applications.map(obj => obj.videoId[0])
console.log('this click', index)
}
render() {
// console.log ('myProps', this.props.applications[0]);
const {applications} = this.props;
return (
<div>
{
applications.map((app, k) => {
return (
<Card key={k} style={styles.appCard}>
<CardMedia style={styles.appMedia}>
<div>
<Drafts color={white} style={styles.iconer}/>
</div>
</CardMedia>
<CardTitle key={k} title={app.videoId} subtitle="Application"/>
{/* <div key={k}><h3>{app}</h3></div> */}
<CardText>
<div>
<div>Status:
<b>test</b>
</div>
</div>
</CardText>
<FloatingActionButton
style={styles.addButton}
backgroundColor={'#CC0000'}
onClick={this.handleClick.bind(this, k)}
>
<ContentAdd/>
</FloatingActionButton>
</Card>
)
})
}
</div>
);
}
}
What I've tried so far:
if I use:
index = this.props.applications[0]
I get the first value of the array on ALL buttons I click on and
If I use:
index = this.props.applications.map(obj => obj.videoId[0])
I get the first letter of every single item of the array inside another array on every click, Is there any way I can get the value of the element I've clicked on , if so how?
When you map over an array you provide a function where the first argument is the current item, and the second one is the current index (of that item).
someArray.map((currentItem, currentIndex) => /* do stuff */ )
If you just care about the item, then there is no need to involve the index. You could just pass the current item directly to handleClick.
render() {
const {applications} = this.props;
return (
<div>
{
applications.map((item) => {
<FloatingActionButton
style={styles.addButton}
backgroundColor={'#CC0000'}
onClick={ this.handleClick.bind(this, item) }
>
</Card>
)
})
}
</div>
);
handleClick would then deal directly with the item, not the index. If you still want to use indexes, perhaps because that's nice to have if you need to manipulate the array at some later stage, then the index (second argument), let's call it index, could be passed to handleClick as before, and you would use that index to find the relevant item with
const clickedItem = this.props.applications[index]
index = this.props.applications[index]
or
index = this.props.applications[index].videoid

Categories

Resources