Home > Enterprise >  useState for mapped buttons
useState for mapped buttons

Time:04-20

I have several cards mapped from an API. Each card has a button, that when clicked, expands the grades section for each card. My issue is that, ALL buttons only trigger the first card.

function showGrades() {
    let showAllGrades = document.getElementById('allGrades')
     
    if (showAllGrades.style.display === '' || showAllGrades.style.display === 'none') {
      showAllGrades.style.display = 'inline-block'
    } else {
      showAllGrades.style.display = 'none'
    } 
  }
 

  return (
    <>
      <Card className={"d-flex flex-row flex-wrap border-0"}>
        <Card.Img className='m-2' src={student.pic} style={{width:'8em', height:'8em', border:'1px solid black', borderRadius:'100px'}} />

        <Card.Body>
          <Card.Title as='h1'><strong>{student.firstName   ` `   student.lastName}</strong></Card.Title>
              <span style={{float:'right'}}>
                <Button onClick={(e) => showGrades(e.target.id)} type='button' style={{background:'none', border:'none'}}>
                <i className='fa fa-plus' style={{fontSize:'24px', color:'#b2b2b2'}}></i>       
                </Button>
              </span>
          <Card.Text className="ps-4" as='p'>Email: {student.email}</Card.Text>
          <Card.Text className="ps-4" as='p'>Company: {student.company}</Card.Text>
          <Card.Text className="ps-4" as='p'>Skill: {student.skill}</Card.Text>
          <Card.Text className="ps-4" as='p'>Average: {student.grades.reduce((a, b) => a   parseFloat(b), 0) / student.grades.length}</Card.Text>
            <div id='allGrades' style={{paddingLeft:'1.5rem', display:'none'}}>
                      {student.grades.map((grade, index) => {
                        return (
                    <>
                    <div className='d-flex flex-column justify-content-center'>Test {index   1} {grade}</div>
                    
                    </>
                  )
                })}
          </div>
          <span style={{paddingLeft:'1.5rem'}}>
            <Button type='button' className='bg-light' style={{color:'#000'}}>new tag</Button>
          </span>
        </Card.Body>
      </Card>
      
      
    </>
  )
}

Sandbox to see what's actually going on: https://codesandbox.io/s/optimistic-keldysh-9cqgxy?file=/src/App.js

Thanks

CodePudding user response:

Firstly, to immediately fix your problem, you'll need to use a unique id for each card. By creating a unique identifier suffix, you can avoid errors with getElementById('allGrades').

function showGrades(indexFromParentMapping) {
  let showAllGrades = document.getElementById(
    `allGrades-${indexFromParentMapping}`
  );

 ...
}

In general, it is bad practice to use getElementById within a react app, as you can already access a specific element without needing to grab an id, since you have an event associated with your button click on each independent card. consider using react to your advantage and passing a value into the function that you can use to smartly access a more complex state object stored a level up in the parent, instead of on the child.

Your card render will eventually look like this, with the function and state to open and view graded above lifted to the parent:

 ...<div id={`allGrades-${props.index}`} ... />...

read more here: https://reactjs.org/docs/lifting-state-up.html

CodePudding user response:

Open the console, and you can find the error, there itself :)

You are setting muliplte div's with the same id, which is not possible, since id is unique. So, except for first, other nodes are not added to the DOM.

What you can do is, when setting id, create a unique id, maybe, id = "allGraders" some unique student id, and then on clicking of the expand button pass that unique student id as a param to the function called. There search for that id

  const id = "allGrades"   id
    let showAllGrades = document.getElementById("allGrades");
  • Related