Problem with indexOf in an array of objects. React app - javascript

I'm having a following situation where I want to know the indexOf value to be able to use that knowledge later on in the code. How ever, I've tried multiple different ways to get the value and I don't seem to get it right. In the code below I've tried a few different ways that I found searching Stackoverflow. All of them return -1 so far, meaning that there is either something wrong with my code or some other issue I'm not able to find at the moment.
FYI, selectedGroup is an array with objects inside, just like this:
[{label: "somelabel", value: 100}]
and there can be many of them, depends on the user.
code:
import React, { useState, useEffect } from 'react';
const GroupButtonMaker = ({ selectedGroup}) => {
const [newButtons, setNewButtons] = useState([]);
console.log(newButtons);
useEffect(() => {
const createButtons = () => {
setNewButtons(
selectedGroup.map(item => {
return (
<button
id={item.value}
className={'btn green micro'}
key={item.value}
onClick={event => btnHandler(event)}
>
{item.label}
</button>
);
})
);
};
createButtons();
}, [selectedGroup]);
const btnHandler = event => {
//here at the handler I'm trying multiple different ways to find indexOf as a test. No luck so far.
const eventID = event.currentTarget.id;
let currentTargetIndex = newButtons
.map(item => item.value)
.indexOf(eventID);
console.log(currentTargetIndex);
console.log(newButtons.findIndex(x => x.value === eventID));
};
Array.prototype.indexOfObject = function arrayObjectIndexOf(property, value) {
for (var i = 0, len = this.length; i < len; i++) {
if (this[i][property] === value) return i;
}
return -1;
};
// here i've hardcored one value as a test to see if it works but it didn't so far.
console.log(newButtons.indexOfObject('value', 107));
const idx = newButtons.reduce(function(cur, val, index, eventID) {
if (val.value === eventID && cur === -1) {
return index;
}
return cur;
}, -1);
console.log(idx);
return <ul>{newButtons.map(item => item)}</ul>;
};
export default GroupButtonMaker;
Thank you beforehand for any suggestions to my current problem. Hopefully I've managed to describe the problem in a way that makes it solveable. If not, please ask and I'll try to provide an answer.

Why not simply pass the id of the button to the handler instead of getting it from event.
You can achieve it by this: onClick={(event) => btnHandler(item.value)}
And then in your btnHandler, just look up the index of the selected button from selectedGroup instead of newButtons.
Here, give this a try:
import React, { useState, useEffect } from "react";
const GroupButtonMaker = ({ selectedGroup }) => {
const [newButtons, setNewButtons] = useState([]);
useEffect(() => {
const buttons = selectedGroup.map(item => {
return (
<button
id={item.value}
className={"btn green micro"}
key={item.value}
onClick={(event) => btnHandler(item.value)}
>
{item.label}
</button>
);
});
setNewButtons(buttons);
}, [selectedGroup]);
const btnHandler = buttonId => {
const selectedButtonIndex = selectedGroup.findIndex(item => item.value === buttonId);
console.log("selectedButtonIndex is: ", selectedButtonIndex);
};
return <ul>{newButtons.map(item => item)}</ul>;
};
export default GroupButtonMaker;
Here's a Working Sample Code Demo for your ref.

Related

Manipulating Local Data in React JS, Can Not Get It To Work For My Quiz Application

I'm new to coding (it's been around three months) and I have a problem with React JS.
I took freecodecamp's eleven hour REact JS Course on YouTube and in the end of the video, there is a quiz application challenge called quizzy.
You can go to my github project file and check it out
I came to a point where I can't get the answer options selected.
I want to toggle between a different colored background whenever I click on an answer button, and I wanted it to stay as long as that button is clicked. As far as I checked, there seems to be a problem with the App.js file where I try to manipulate the data's isSelected key inside toggle function. I kindly ask anyone for help. I just don't know what I am doing wrong and it's driving me crazy.
My App.js file looks like this:
import { nanoid } from 'nanoid';
import React from 'react';
import data from '../data';
import QuestionsAndAnswers from './QuestionsAndAnswers';
function Quiz() {
const [quiz, setQuiz] = React.useState(data);
// const [isSelected, setIsSelected] = React.useState(false);
React.useEffect(() => {
const newData = data.map((data) => ({
...data,
answerOptions: data.answerOptions.map(answerOptions => ({
...answerOptions,
optionsID: nanoid()
}))
}))
setQuiz(newData);
}, [])
const handleSubmit = (event) => {
event.preventDefault();
console.log("completed")
}
function toggle(id, value) {
console.log(id, value)
setQuiz((oldState) => oldState.map((data) => {
return data.id === id
? {
...data,
answerOptions: data.answerOptions.map(answerOptions => {
return answerOptions.answerText === value
? {
...answerOptions,
isSelected: !answerOptions.isSelected
}
: {
...answerOptions,
isSelected: false
}
})
}
: data
}))
}
const selectedOptions = data.map(data => {
return (data.answerOptions.isSelected ? data : null)
})
console.log(selectedOptions)
const questions = quiz.map((quiz, index) => {
return (
<QuestionsAndAnswers
key={index}
quiz={quiz}
setQuiz={setQuiz}
toggle={toggle}
/>
)
})
// main function
return (
<main>
<form className="form-container" onSubmit={handleSubmit}>
<h2 className='header'>QuizCript</h2>
{questions}
<button className="complete-quiz-button" type='submit'>Complete the Quiz</button>
</form>
</main>
)
}
export default Quiz;

Need help printing out a list from db

Been trying to print out a simple list from db for 2 days now, here's the code right now:
function CategoriesTable() {
const [isLoading, setLoading] = useState(true);
let item_list = [];
let print_list;
useEffect(() =>{
Axios.get('http://localhost:3000/categories').then((response) => {
const category_list = response.data.result;
if(category_list) {
for(let i = 0; i < category_list.length; i++){
item_list.push(category_list[i].category_name)
}
}
print_list = function() {
console.log(item_list.map((item) => <li>item</li>))
return item_list.map((item) => <li>item</li>)
}
setLoading(false);
})
}, [])
return (
<div>
{ !isLoading && print_list }
</div>
)
}
I think the function should be executed after the loading state gets changed to false, right? For some reason the function is not executing
By the way, I can print out the list in console without a problem, rendering the list is the problem.
I would suggest to do something like this:
function CategoriesTable() {
const [fetchedData, setFetchedData] = useState({
result: [],
isLoading: true,
});
useEffect(() =>{
Axios.get('http://localhost:3000/categories').then(response => {
const category_list = response.data.result;
setFetchedData({
isLoading: false,
result: category_list.map(category => <li>{ category.category_name }</li>),
})
})
}, [])
return (
<div>
{ !fetchedData.isLoading && fetchedData.result }
</div>
)
}
Basically rewrite from the ground up since the original code is quite messy I'm afraid.
Feel free to ask in the comments if you have any questions.
import { useState, useEffect } from "react";
import Axios from "axios";
/**
* Why mix cases here? Are you gonna use camel or snake case? Choose one and only one.
*/
function CategoriesTable() {
const [categoryNames, setCategoryNames] = useState([]);
useEffect(() => {
// not a good idea to use full URL on localhost
Axios.get('/categories').then((response) => {
const categoryList = response.data.result;
if (categoryList) {
const categoryNames = categoryList.map(({ category_name }) => category_name);
console.log(categoryNames); // in case you want ot keep the console output
setCategoryNames(categoryNames);
}
});
}, []);
return (
<div>
{/* You should either wrap <li> with either <ol> or <ul> */}
<ol>
{categoryNames.map(categoryName => (
// key is required and should be unique in map statement. In here I assume there are no duplicated categoryName
<li key={categoryName}>{categoryNames}</li>
))}
</ol>
</div>
);
}

How to set the state when it is an array of objects

So i want to build a voting app thing, and I need to set the number of votes into state so that it updates on click.But I don't exactly know how to set one property of an object without disrupting the other oroperty and the other object.
import './App.css';
import React, {useState} from 'react'
function App() {
const [votes, setVotes] = useState([
{
voteNum: 0,
name: "Item 1"
},
{
voteNum: 0,
name: "Item 2"
}
])
const addVote = (vote) => {
vote.voteNum++
setVotes( /* How do I set the votes.voteNum to be equal to vote.voteNum from the line above */)
}
return (
<div className="App">
<h1>Cast Your Votes!</h1>
{votes.map(vote => (
<form className="votee" onClick={() => addVote(vote)}>
<h1>{vote.voteNum}</h1>
<h2>{vote.name}</h2>
</form>
))}
</div>
);
}
export default App;
You should update react state in immutable way:
const addVote = (vote) => {
setVotes(votes.map(x => x === vote ? ({
...x,
voteNum: x.voteNum + 1
}) : x))
}
Or better use some unique Id in comparison, e.g. x.id === vote.id if you have Ids for your objects; or as mentioned in another answer, you can also use element array index in comparison (pass that to the function instead of vote).
const addVote = (vote) => {
const _votes = JSON.parse(JSON.stringify(votes))
const index = _votes.findIndex(v => v.name === vote.name)
if (index !== -1) {
_votes[index].voteNum ++
}
setVotes(_votes)
}
Pass index to function
{votes.map((vote, index) => (
<form className="votee" onClick={() => addVote(index)}>
<h1>{vote.voteNum}</h1>
<h2>{vote.name}</h2>
</form>
))}
addVote function would look like
const addVote = (index) => {
votes[index].voteNum ++;
setVotes([...votes]);
}
Note: Usestate is prefrered to use primitive data types i.e string, boolean and number
For array and object make use of useReducer

Deleting Objects in State While Rendering State in Reverse

Making a comment section for a website and I ran into a big problem. Currently I have a delete button that splices the comments from state based on their index. I need to show the array in reverse to the user--so when they make multiple comments the newest one is ontop.
The problem is if I reverse() the mapped array the index doesn't get reversed with it, so clicking delete for item 1 deletes the last item, and vice versa.
const [userComments, setUserComments] = useState([])
const postComment = (event, userComment) => {
if (event.key === 'Enter') {
setUserComments(prevState => ([...prevState, {comment: userComment}]))
}
}
const deleteComment = (e, i) => {
const userCommentsArray = [...userComments]
userCommentsArray.splice(i, 1)
setUserComments(prevState => ([...prevState], userCommentsArray))
}
return (
<input
placeholder="Add a public comment"
onKeyUp={event => postComment(event, event.currentTarget.value)}
onClick={event => showCommentButtons()}
/>
{ userComments
? userComments.map((item, i) => (
<div className="button" onClick={e => deleteComment(e, i)}>Button</div>
<p className="comment">{item.comment}</p>
))
: null
}
)
Use reverse method on array:
const deleteComment = (e, i) => {
const userCommentsArray = [...userComments].reverse()
userCommentsArray.splice(i, 1)
setUserComments(prevState => ([...prevState], userCommentsArray.reverse()))
}
Figured it out. Used unshift to push the items to state in reverse order.
No other changes necessary.
const postComment = (userComment) => {
const userCommentNotBlank = !userComment.trim().length < 1
if (userCommentNotBlank) {
const newState = [...userComments]
newState.unshift(userComment)
setUserComments(prevState => ([...prevState], newState))
resetAddComment()
}
}

Wrong UI Component Deleted When Validating User Input React

essentially I have a KanbanBoard-ish app I'm trying to develop and I'm getting some strange behavior when I call my delete function from my validation function. The code is here on codesandbox. The main issue is that when there are multiple cards and I try to delete a card with an onBlur event, the card where the event occurs is not deleted but another empty card is. It works as expected if all other cards in a column have text. Please ignore the dnd code, as it came after the original problem.
Thanks in advance.
EDIT:
Here is the logic with App.js
state = { list: list }
handleChange = (e, col) => {
let eid = parseInt(e.target.id)
let updatedList = this.state.list.map(obj => {
if (obj.id === col) {
let card = { text: e.target.value }
obj.cards[eid] = card
}
return obj
})
this.setState({ list: updatedList })
}
setText = (e, col) => {
if (e.target.value === "") {
e.target.placeholder = "YOU MUST ENTER TEXT. THIS BOX WILL CLOSE NOW"
e.persist()
setTimeout(() => {
this.delete(e, col)
}, 3000)
return
}
}
delete = (e, col) => {
e.preventDefault()
let eid = parseInt(e.target.id)
let updatedList = this.state.list.map(obj => {
if (obj.id === col) {
obj.cards = obj.cards.filter((c,i) => i !== eid)
//obj.counter--
}
return obj
})
this.setState({ list: updatedList })
}
add = e => {
e.preventDefault()
let eid = parseInt(e.target.id)
let updatedList = this.state.list.map(obj => {
if (obj.id === eid) {
let card = {text:""}
obj.cards.push(card)
obj.counter++
}
return obj
})
this.setState({ list: updatedList })
}
}
map returns an item for each item it iterates through. Maybe using filter would help in the case. I'm assuming that your splice is making the order of this.state.list get crazy and confusing.
let updatedList = this.state.list.filter(obj => obj.id !== col);
Not sure if col or eid is the correct thing to compare to, but that will get you a new list with all of the previous items except for the one whose id matches the id you're trying to delete.
Glancing at your codesandbox, there are some issues. To boil it down to a high level - each card should have an immutable ID, that you can use to delete it. You're using the index of the card in an array and combined with who knows what else. You've lost your source of truth, which is extra important when you are allowing the user to alter the order of an array. Your card should fire the delete function you pass it from its parent. It should just take the id of that card, filter that out of the current state, and set the new state. You're making this overcomplicated.
Parent -
state = { list : [{id: 1, ...other card stuff}, {...more cards}] };
delete = id => {
const newList = this.state.list.filter(item => item.id !== id);
this.setState({ list: newList };
}
render = () => {
const { list } = this.state;
return list.map(item => (
<Card
{...item}
onDelete={this.delete}
/>
))
}
Card -
// whenever you need to delete this card
this.props.onDelete(this.id);

Categories