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.