How do I target specific button for click event - reactjs

When I click on the + every list is displayed and if I click on the - everything is hidden. How can I click on specific button and it displays the content or hide as the case may be. Right now a click on any of the button either hides or shows.
{ showing ? <button onClick={(e) => setShowing(false)}>-</button> : <button onClick={(e) => setShowing({showing: showing})}>+</button>
}
{ showing
? student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
)) : <span></span>
}

Your plus function seems to be invalid, try something like this:
For the minus (-):
<button onClick={(e) => setShowing(false)}>-</button>
For the plus (+):
<button onClick={(e) => setShowing(true)}>+</button>

You either need to keep track of a showing state variable for each row, or a more "React" style solution would be to create another component for each row, and let them each have one state variable:
function myComponent(props) {
const { students } = props.students; // or wherever these come from - you didn't specify
return students.map((student) => <StudentRow {...student} />});
}
function StudentRow (props) {
const [showing, setShowing] = useState(false); // or true if they default visible
return (<>
{ showing ? <button onClick={(e) => setShowing(false)}>-</button> : <button onClick={(e) => setShowing(true)}>+</button>}
{ showing ? student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
)) : <span></span>}
</>);
}
You could refactor this StudentRow component to be more easily readable as:
function StudentRow (props) {
const [showing, setShowing] = useState(false); // or true if they default visible
if (!showing) {
return <button onClick={(e) => setShowing(true)}>+</button>;
} else {
return (<>
<button onClick={(e) => setShowing(false)}>-</button>
{student.grades.map((grade, index) => (
<span className="grade" key={index}>Test {index}: {grade}</span>
))}
</>);
}

Related

React JS show and hide

I have a register page for two types of users as tabs, so when I click one kind of user the other should be hidden and the clicked user tab should be displayed. I tried useState, and the hiding works but the desired user form is not rendered. I'm sure I'm missing something here.
const [customerForm, setCustomerForm] = useState(true);
const [feForm, setFEForm] = useState(false);
const FEToggle = () =>{
customerForm ? setCustomerForm(false) : setCustomerForm(true)
feForm ? feForm(false): setFEForm(true)
}
const CTRToggle = () =>{
feForm ? setFEForm(false) : setFEForm(true)
customerForm ? setCustomerForm(false): setCustomerForm(true)
}
<button type="button" onClick={() => {FEToggle()}}>
<i className="fa fa-building" />
Financial Enterprise
</button>
<button type="button" onClick={() => {CTRToggle()}}>
<i className="fa fa-user" />
Customer
</button>
feForm ? feForm(false): setFEForm(true)
change to
feForm ? setFEForm(false): setFEForm(true)
Change FEToggle and CTRToggle method
const FEToggle = () => {
setCustomerForm(prev => !prev)
setFEForm(prev => !prev)
}
const CTRToggle = () => {
setCustomerForm(prev => !prev)
setFEForm(prev => !prev)
}

react all classNames are affected in map()

import React from 'react'
import { useState, useEffect } from 'react'
import axios from 'axios'
const Home = () => {
const getSongs = () => {
axios.get('http://localhost:8000/api/songs/')
.then(res => setSongs(res.data))
}
let [songs, setSongs] = useState([])
let [paused, setPause] = useState(true)
useEffect(() => {
getSongs()
}, [])
const toggleSong = (id) => {
const x = document.getElementById(id)
if (x.paused){
x.play()
setPause(false)
} else {
x.pause()
setPause(true)
}
}
// Got rid of the functions that are not needed
return (
<>
{
songs.map(song =>
(
<div className='music-controller' key={song.id}>
<div id={'songDiv'} style={{cursor: 'pointer'}} onClick={(e) => changeSongTime(e, song.id)}>
<div id={`songTime-${song.id}`}></div>
</div>
<div className="music-controller-body">
<div className="music-controller-header">
<h2>{song.title}</h2>
<p><small>{song.genre}</small></p>
</div>
<div className="controls">
// here <----------------------
<i unique={song.id} className={`fas fa-${paused ? 'play' : 'pause'}`} onClick={() => toggleSong(song.id)}></i>
<audio id={song.id} onTimeUpdate={() => songTime(song.id)}>
<source src={`http://localhost:8000/api/songs/audio/${song.id}`} />
</audio>
</div>
</div>
</div>
))}
</>
)
}
export default Home
Whenever I click on a specific i element all of the i elements that were not clicked on get changed too.. to put it simply when I click on the 1st i element only its className should change, but all of the i elements classNames are affected, what is causing this?
I think you should use event.target
const handlePlay = (song) => {
song.play();
};
const handlePause = (song) => {
song.pause();
};
...
<div className="controls">
<i
onMouseOver={(e) => handlePlay(e.target)}
onMouseLeave={(e) => handlePause(e.target)}
className={`fas fa-${paused ? 'play' : 'pause'}`}
onClick={() => toggleSong(song.id)}>
</i>
<audio id={song.id} onTimeUpdate={() => songTime(song.id)}>
<source src={`http://localhost:8000/api/songs/audio/${song.id}`} />
</audio>
</div>
I don't think Toggle would work in this case, an action should happen so it knows when it should stop.
Can you put console in toggleSong function at top and check if you are getting correct id. If you are not getting single Id then work is needed with onClick. So, after that also try passing id like this
onClick={(song?.id) => toggleSong(song?.id)}
then see console again and look for correct id if it is displayed or not. I think your className is not updating due to this issue.
One thing more you can try at end is replacing with this
const x = id; //without document.getElementById
const toggleSong = (e, id) => {
const x = document.getElementById(id)
const button = e.currentTarget
if (x.paused){
x.play()
button.className = 'fas fa-pause'
} else {
x.pause()
button.className = 'fas fa-play'
}
}
<i unique={song.id} className='fas fa-play' onClick={(e) => toggleSong(e, song.id)}></i>
I fixed this by just getting the current target with event.currentTarget and change its className accordingly!

How to properly get a react fresh state before rendering the component?

I'm sorry if the answer is obvious. I probably missed a pattern to do that...
I have this kind of architecture (more complexe, of course) :
A component (here a button) have to call a function and to set a state in the same time.
Of course, This example don't work because the state to displayed word isn't defined before the speak() function is called.
What is the best way to do this ?
const Speaker = () => {
const [word, setWord]= useState ("nothing")
const speak = () => {
console.log(word)
...
}
const speakDirectly = () => {
setWord("hello")
speak() // say "nothing" instead of "hello" 😞
}
const prepareWord = (wordToPrepare) => {
setWord(wordToPrepare)
}
return (
<>
<p>Thinking word : {word} </p>
<button onClick={() =>prepareWord('Goodbye')} >Prepare to say "Goodbye"</button>
<button onClick={() =>prepareWord('Goodnight')} >Prepare to say "Goodnight"</button>
----
<button onClick={() =>speak()} >Say Hello</button>
<button onClick={() =>speakDirectly('hello')} >Say Hello</button>
</>
)
}
Does exists a pattern to resolve this behaviour in React?
I want to avoid to update my functions like this 🤢
const Speaker = () => {
const [word, setWord]= useState ("nothing")
const speak = (directWord) => {
let wordToSay = directWord || word
console.log(word)
...
}
const speakDirectly = (directWord) => {
setWord(directWord)
speak(directWord)
}
...
You could do something like this. Have a state for your word and your direct word. Use an effect for your direct word to trigger speak whenever it's updated, but call speak on click for your prepared word.
const Speaker = () => {
const [preparedWord, setPreparedWord]= useState();
const [directWord, setDirectWord]= useState();
const speak = (word) => {
console.log(word)
...
}
useEffect(() => directWord && speak(directWord), [directWord]);
return (
<>
<p>Thinking word : {preparedWord} </p>
<button onClick={() => setPreparedWord('Goodbye')} >Prepare to say "Goodbye"</button>
<button onClick={() => setPreparedWord('Goodnight')} >Prepare to say "Goodnight"</button>
----
<button onClick={() => speak(preparedWord)} >Speak</button>
<button onClick={() => setDirectWord('hello')} >Say Hello</button>
</>
)
}
Update
After thinking about it for a second, unless you need the direct word to be in your state, you can get rid of that part too and just call speak on it while passing it to the function.
const Speaker = () => {
const [preparedWord, setPreparedWord]= useState();
const speak = (word) => {
console.log(word)
...
}
return (
<>
<p>Thinking word : {preparedWord} </p>
<button onClick={() => setPreparedWord('Goodbye')} >Prepare to say "Goodbye"</button>
<button onClick={() => setPreparedWord('Goodnight')} >Prepare to say "Goodnight"</button>
----
<button onClick={() => speak(preparedWord)} >Speak</button>
<button onClick={() => speak('hello')} >Say Hello</button>
</>
)
}

All buttons are clicked at the same time instead of the specific one clicked

I am much confused as I don't know what I am doing wrong. Each time I clicked on the plus sign, all the other div elements display instead of the specific one I click on. I tried to use id argument in my show and hide functions, it is complaining of too many re-rendering . I have been on this for the past 12 hours. I need your help to solving this mystery. All I want to do is to click on the plus sign to display only the content and minus sign to hide it.
import React, {useState, useEffect} from 'react'
function Home() {
const [userData, setUserData] = useState([]);
const [showing, setShowing] = useState(false)
const [search, setSearch] = useState("");
const [clicked, setClicked] = useState("")
async function getData()
{
let response = await fetch('https://api.hatchways.io/assessment/students');
let data = await response.json();
return data;
}
useEffect(() => {
getData()
.then(
data => {
setUserData(data.students ) }
)
.catch(error => {
console.log(error);
})
}, [])
const handleFilterChange = e => {
setSearch(e.target.value)
}
function DataSearch(rows) {
const columns = rows[0] && Object.keys(rows[0]);
return rows.filter((row) =>
columns.some((column) => row[column].toString().toLowerCase().indexOf(search.toLowerCase()) > -1)
);
}
const searchPosts = DataSearch(userData);
const show = (id, e) => {
setShowing(true);
}
const hide = (id, e) => {
setShowing(false);
}
return (
<>
<div>
<input value={search} onChange={handleFilterChange} placeholder={"Search by name"} />
</div>
{
searchPosts.map((student) => (
<div key={student.id} className="holder">
<div className="images">
<img src={student.pic} alt="avatar" width="130" height="130" />
</div>
<div className="data-container">
<span className="name">{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}</span>
<span>Email: {student.email}</span>
<span></span>
<span>Company: {student.company}</span>
<span>Skill: {student.skill}</span>
<span>City: {student.city}</span>
{ showing ?
<button id={student.id} onClick={hide}>-</button>
: <button id={student.id} onClick={show}>+</button>
}
<div data-id={student.id}>
{ (showing )
? student.grades.map((grade, index) => (
<span id={index} key={index}>Test {index}: {grade}%</span>
)) : <span>
</span>
}
</div>
</div>
</div>
))
}
</>
)
}
export default Home
Change,
const [showing, setShowing] = useState(false)
to:
const [showing, setShowing] = useState({});
Here change the useState from boolean to object.. Reason for this is we will store the ids as keys and a boolean value indicating if the grade should be shown or not.
And remove Show and hide function and have a common toggle function like,
const toggleGrades = (id) => {
setShowing((previousState) => ({
...previousState,
[id]: !previousState[id]
}));
};
You are using setShowing(true) in show function and setShowing(false) in hide function which is the reason for opening all and closing all at any click.. Because you have never mentioned which exact grade should be shown so you need to make use of id here..
And buttons click handler will be like,
{showing[student.id] ? (
<button id={student.id} onClick={() => toggleGrades(student.id)}>
-
</button>
) : (
<button id={student.id} onClick={() => toggleGrades(student.id)}>
+
</button>
)}
So pass student id () => toggleGrades(student.id) in both show and hide button an make the button gets toggled.
Display the grades like,
<div data-id={student.id}>
{showing[student.id] ? (
student.grades.map((grade, index) => (
<span id={index} key={index}>
Test {index}: {grade}%
</span>
))
) : (
<span></span>
)}
</div>
Here if showing[student.id] will display only the grades of clicked item.
And that is why id plays a major role in such case.
Working Example:

Button's onClick not returning id

I'm trying to read the id of a <Button /> component when it's clicked:
const FlagsDialog = (props) => {
const {
classes,
handleClose,
showFlagsDialog,
} = props;
const selectLang = (evt) => {
console.log('CLICKED', evt.target.id);
};
return (
<Dialog
open={showFlagsDialog}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<Button onClick={selectLang} id="en">English</Button>
<Button onClick={selectLang} id="es">Spanish</Button>
<Button onClick={selectLang} id="fr">French</Button>
<Button onClick={selectLang} id="de">German</Button>
</Dialog>
);
};
But when run, the click only returns the text CLICKED, and no value for the clicked button's id. What am I doing wrong?
I don't know what you're trying to do, but that code works.
I'm assiming Button and Dialog are your custom components (if not, you need to change them to button and dialog). I changed them to button and dialog in the code that I used and it works fine.
Here, check it out:
use a method instead of event for passing id as parameter.
const FlagsDialog = (props) => {
const {
classes,
handleClose,
showFlagsDialog,
} = props;
const selectLang = (id) => {
console.log(id);
//OR e.target.getAttribute("id")
};
return (
<Dialog
open={showFlagsDialog}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<Button onClick={() => this.selectLang(id)} id="en">English</Button>
<Button onClick={selectLang} id="en">English</Button>
</Dialog>
);
};
Alternatively you can use the fancy-pant ref function:
class App extends React.Component {
btns = [];
handleClick = index => console.log(this.btns[index].id);
render() {
return (
<div>
<button
ref={ref => (this.btns[0] = ref)}
onClick={() => this.handleClick(0)}
id="no1"
>
First
</button>
<button
ref={ref => (this.btns[1] = ref)}
onClick={() => this.handleClick(1)}
id="no2"
>
Second
</button>
</div>
);
}
}
Turns out this ref implementation is simpler than I expected.

Resources