Home > front end >  React - How can the parent know which children was clicked?
React - How can the parent know which children was clicked?

Time:10-07

I have a React component, that I want to make re-usable and use in multiple pages in my app, that takes a number of children (multiple instances of the same component).
Imagine react-select. In the most basic usage, you provide a list of options a selected value and an onChange function. All of these are provided as props of Select.

I want to implement something somewhat similar, but that takes the "options" as children elements instead of a prop, and the rest as props of the parent.

Right now I have something this:

enum Children {
  first = "first",
  second = "second",
  // ...
}

const [child, setChild] = useState(Children.first)

<ChildrenSelect>
  <Child
    selected={child === Children.first}
    onClick={() => setChild(Children.first)}
  />
  <Child
    selected={child === Children.second}
    onClick={() => setChild(Children.second)}
    // more props 
  />
  {/* ... potentially more children */}
</ChildrenSelect>

Here I have the state of which child is selected managed by the outermost component.
I would like to move the selected and onClick props from the children to the ChildrenSelect component, in order to have more compact children:

const childClickHandler = (c: Children) => setChild(c);

<ChildrenSelect
  selected={child} //: Children
  onChildClick={childClickHandler} //: (c: Children) => void
>
  <Child id={Children.first} />
  <Child id={Children.second} />
  {/* ... potentially more children */}
</ChildrenSelect>

But how does the ChildrenSelect component know which Child was clicked?


EDIT: I'm nearly there!

Based on this and this I managed something that looks close to the desired solution.

You can loop trough children inside the ChildrenSelect component and have the parent modify the children's props.

So the code for the ChildrenSelect component would be something like this:

// ChildrenSelect
import React, { Children, BaseHTMLAttributes, FC, cloneElement, ReactElement } from "react";

type Props = {
  // ChildrenSelectspecific props
} & BaseHTMLAttributes<HTMLDivElement>;

export const ChildrenSelect: FC<Props> = ({ children }) => {
  const childrenArray = Children.toArray(children);

  return (
    <div className="children-select">
      <div>{/* other parent stuff */}</div>
      {Children.map(childrenArray, (child, i) => {
        console.log("child is", child);
        return cloneElement(child as ReactElement<ChildComponentProps>, {
          onClick: () => {
            console.log("clicked", i);
          },
          text: `modified child ${i}`,
        });
      })}
    </div>
  );
};

But somehow the onClick is not being called.

In the DevTools I can see that text has been set correctly but onClick apparently hasn't:

props
  onClick: ƒ onClick() {}
  text: "modified child 0"

CodePudding user response:

based on my comment when i understand ur question correct.

function handler(e)=>{

console.log("clicked " e.target.id) //maybe its e.currentTarget.id
}

let childArr = [];
for(let i = 0; i < 100; i  ){
childArr.push(  <Child key={"child" i} id={"child" i} onClick=(e => handler(e)) />);
}

<Parent
  selected={child} // : Children
  onChildClick={childClickHandler} // : (c: Children) => void
>
{childArr}
</Parent>

CodePudding user response:

First of all, change

onClick={firstChildClickHandler()}

to

onClick={childClickHandler}

The difference here is the first one calls the function and gets the return value, which then becomes the onclick handler. The second one sets the onlcick handler directly as the function, which is what you want.

Second, change the clickhandleer to take a parameter:

const childClickHandler = (e) = {/* do stuff */}

Now you can access the item that was clicked on with e.target.

You can take this even further and add whatever parameters you want to childClickHandler. Then in the Child component, you call the onClick with the appropriate parameters for that particular child.

  • Related