I am trying to create a table component where the component will serve some special className to its specific children.
const BasicTable = ({ children }) => {
const RenderChild = Children.map(children, (el) => {
const child = el;
if (child !== null) {
if (child.props.originalType !== "th") {
return <child.type {...child.props} className="th" />;
}
return <child.type {...child.props} />;
}
return null;
});
return (
<div className="table-responsive">
<table className="table w-full bg-transparent">{RenderChild}</table>
</div>
);
};
This is my component and I want to use it like this.
<BasicTable>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Position</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<tr>
<td>dsfa</td>
<td>dsfa</td>
<td>dsfa</td>
<td>dsfa</td>
</tr>
</tbody>
</BasicTable>
But here the problem is my Children.map function only loop through its immediate children. How do I pass props to its nested child (th, td, ...etc)
You can define a recursive function to iterate over all the children until thi child is null
or is of type string
or any other condition based on your requirement, here is an example:
const BasicTable = ({ children }) => {
const iterateOverChildren = (children) => {
return React.Children.map(children, (child) => {
// equal to (if (child == null || typeof child == 'string'))
if (!React.isValidElement(child)) return child;
return React.cloneElement(child, {
...child.props,
// you can alse read child original className by child.props.className
className: child.type == 'th' ? 'th' : '',
children: iterateOverChildren(child.props.children)})
})
};
return (
<div className="table-responsive">
<table className="table w-full bg-transparent">
{iterateOverChildren(children)}
</table>
</div>
);
};
function App() {
return (
<BasicTable>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Position</th>
<th>Salary</th>
</tr>
</thead>
<tbody>
<tr>
<td>dsfa</td>
<td>dsfa</td>
<td>dsfa</td>
<td>dsfa</td>
</tr>
</tbody>
</BasicTable>
);
}
ReactDOM.render(<App/>, document.getElementById('root'))
.th{
color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can provide children
prop after applying the classNames
to the current child
. You can define another function to apply custom class names as per your needs (The logic can be changed as per your needs).
This would also work.
import { Children, isValidElement } from "react";
const BasicTable = ({ children }) => {
const RenderChild = (children) => {
return Children.map(children, (child) => {
if (isValidElement(child)) {
return (
<child.type
{...child.props}
children={RenderChild(child.props.children)}
className={resolveCalssName(child.type)}
/>
);
}
// non react elements (text inside the table ...etc)
return child;
});
};
const resolveCalssName = (type) => {
switch (type) {
case "thead":
return "custom-thead-class-name";
case "tbody":
return "custom-tbody-class-name";
case "tr":
return "custom-tr-class-name";
case "th":
return "custom-th-class-name";
case "td":
return "custom-td-class-name";
default:
return "";
}
};
return (
<div className="table-responsive">
<table className="table w-full bg-transparent">
{RenderChild(children)}
</table>
</div>
);
};