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: