Home > OS >  How to pass down function (with id-prop) two level in React JS?
How to pass down function (with id-prop) two level in React JS?

Time:12-10

I'm encountering a problem in my React js project, that has to do with passing down a handleClick function (with callback and some id-prop) as I try to manage my state in the top level component. What I want to achieve is that as I click an answer, the id gets passed to the top level, so I can set the state properly.

My data structure looks like this: An array of 5 objects

(5) [{…}, {…}, {…}, {…}, {…}]
0: {answers: {…}, question: "some text", …} 
1: {answers: {…}, question: "some text", …} 
2: {answers: {…}, question: "some text", …} 
3: {answers: {…}, question: "some text", …} 
4: {answers: {…}, question: "some text", …}

Inside, the answers objects look like this:

answers: Array(4) // or Array (2) for boolean type answers 
0: {answer: '1789', isCorrect: true, isClicked: false, answerId: '5Eh6V2AqoMdGlHadYdtXv'} 
1: {answer: '1823', isCorrect: false, isClicked: false, answerId: 'V8yW9u2oHnZqlRjY-gR7A'} 
2: {answer: '1756', isCorrect: false, isClicked: false, answerId: 'HNWO55rWQFQvrheGEjwAM'} 
3: {answer: '1799', isCorrect: false, isClicked: false, answerId: 'o5BjyE3IJsAcWFEWQJnMA'}

The basic idea is that the App component has all the information regarding the questions and answers and is responsible for state management at the top level. Inside, there is a Main component for questions and and within that an Answer component, that gets rendered dynamically, since there are two types of answers (boolean with two answer possibilities and multiple with four answer possibilities).

<App> 
  <Main> 
    <Answer/> 
  </Main> 
</App> 

The structure of the code inside App is like follows:

const [triviaData, setTriviaData] = useState([]) // data come from api fetch 

function handleClick (id) { 
// here I would like to get back the id derived from from props from the clicked answer
} 

// here I'm mapping through the array of objects that I'm managing in my state and passing some props to the Main component
const triviaElements = triviaData.map(item => { 
return (<Main 
          item={item} 
          key={item.id} 
          handleClick={() => handleClick()} //this is where I struggle /> )})

return ( 
<div> {triviaElements} </div> 
)

Now the Main component:

export default function Main(props) { 

const triviaAnswers = props.item.answers.map(item => { 
return (
  <Answer 
    key={item.answerId} 
    answer={item.answer} 
    isCorrect={item.isCorrect} 
    answerId={item.answerId} 
    handleClick={() => handleClick(item.answerId)} // Problem: props.item.answers has not handleClick /> ) }) 

return( 
<section className="main">
  <h2 className="main--question">{props.item.question}</h2>
  {triviaAnswers}
</section> 
)
} 

And finally the Answer component:

export default function Answer(props) { 
return ( 
<div className="answer" onClick={props.handleClick}>{props.answer}</div>
)
}

Is there a lean way, to pass the function multilevel and get the correct id from the Answer component? Help would greatly appreciated. Regards

Usually if it is only just one level between parent and child and there is not an array, but a single element in the child component, something like this works for me:

const elementsToRender = someArray.map(item => { 
return ( 
<SomeComponent 
  handleClick={()=>handleClick(item.id)} 
/> )
})
return (
  {elementsToRender}
)

In child

<div onClick={props.handleClick}>Some Element</div> 

CodePudding user response:

When you mount the Main component, you're missing the id argument that has to be passed handleClick in App.

<Main 
  item={item} 
  key={item.id} 
  handleClick={(id) => handleClick(id)}
/>

It would be much simpler to simply pass your handleClick function to the handleClick prop, so you don't need to create a brand new arrow function to handle it.

<Main 
  item={item} 
  key={item.id} 
  handleClick={handleClick}
/>

Then, in the Main component itself, you just need to refer to props.handleClick to actually call the handleClick function defined by its parent.

export default function Main(props) {
  const triviaAnswers = props.item.answers.map((item) => {
    return (
      <Answer
        key={item.answerId}
        answer={item.answer}
        isCorrect={item.isCorrect}
        answerId={item.answerId}
        // Pass in the handleClick function from props
        handleClick={() => props.handleClick(item.answerId)}
      />
    );
  });

  return (
    <section className="main">
      <h2 className="main--question">{props.item.question}</h2>
      {triviaAnswers}
    </section>
  );
}

When considering passing handlers/state through many React components and their children, I would recommend looking into React Context (combined with useReducer) to manage drilling state through many components at once.

Docs:

  • Related