Reputation:
Let's say I have a simple react component:
import React from 'react';
import styles from "./index.css";
export default React.createClass({
render: function() {
return (
<header className={styles.root}>
// area for child content
{this.props.children}
</header>
);
}
});
Now let's say that instead of one area for any child components, I'd want two, like so:
import React from 'react';
import styles from "./index.css";
export default React.createClass({
render: function() {
return (
<header className={styles.root}>
<div className={styles.logo}>
// logo children here
</div>
<div>
// navigation children here
</div>
</header>
);
}
});
I know that I could use attributes, but that wouldn't be very elegant for a large chunk of html content. How can this be done in react in a way that's similar to, for example, the named blocks in swig?
Example of named blocks:
{% extends 'layout.html' %}
{% block title %}My Page{% endblock %}
{% block head %}
{% parent %}
<link rel="stylesheet" href="custom.css">
{% endblock %}
{% block content %}
<p>This is just an awesome page.</p>
{% endblock %}
You see that these blocks can be used by referring to their name, and thus allow to 'yield' multiple areas of content. I'm hoping that there is a similarly elegant way in react to do this, as it makes components very composable.
Upvotes: 37
Views: 25442
Reputation: 6803
Make seperate components Donot use props to pass components as children. something like this.
header.js
import React from 'react';
import styles from "./index.css";
export default React.createClass({
getComponent(key) {
return this.props.children.filter( (comp) => {
return comp.key === key;
});
}
render: function() {
return (
<header className={styles.root}>
<div className={styles.logo}>
{this.getComponent('logo')}
</div>
<div>
{this.getComponent('navbar'}
</div>
</header>
);
}
});
app.js
export default React.createClass({
render: function() {
return (
<Header>
<Logo key="logo"/>
<Navbar key="navbar"/>
</Header>
);
}
});
Upvotes: 25
Reputation: 3320
That question inspired me. Here is my approach:
First child component: TestA.tsx
import * as React from 'react';
export default class TestA extends React.Component {
render () {
return (
<div className="TestA">{this.props.children}</div>
)
}
}
Second child component: TestB.tsx
import * as React from 'react';
export default class TestB extends React.Component {
render () {
return (
<div className="TestB">{this.props.children}</div>
)
}
}
Parent component: TestAB.tsx
import * as React from 'react';
type TestABProps = {
children: React.ReactNode;
}
export default class TestAB extends React.Component<TestABProps> {
//Loop through React nodes in children and get all instances of the specified component
getComp( comp: string ): null | React.ReactNode | React.ReactNode[] {
if( this.props.children instanceof Array ) {
let nodes: React.ReactNode[] = [];
for( let child of this.props.children ) {
if( ((child as React.ReactElement)?.type as Function)?.name == comp ) {
nodes.push( child );
}
}
return nodes.length > 1 ? nodes : nodes[0];
}
return null;
}
render () {
return (
<div className="AB">
<div className ="A">
{this.getComp("TestA")}
</div>
<div>This is a DIV between the components</div>
<div className ="B">
{this.getComp("TestB")}
</div>
</div>
)
}
}
As you can see, you only have to place {this.getComp("YourComponentName")}
where you want all the instances of this component to appear in your parent component.
Usage example
<TestAB>
<TestA>Foo</TestA>
<TestB>Bar</TestB>
<TestA>Foobar</TestA>
</TestAB>
Usage is very simple and intuitive. No need for keys or anything else, just insert your child components without weird syntax.
Resulting HTML
<div class="AB">
<div class="A">
<div class="TestA">Foo</div>
<div class="TestA">Foobar</div>
</div>
<div>This is a DIV between the components</div>
<div class="B">
<div class="TestB">Bar</div>
</div>
</div>
Note that you can insert the children of TestAB in any order - they will be inserted in the right place.
All this can certainly be improved, you can adapt this to your needs. For example, if you put any child component other than TestA or TestB, they won't be displayed, that would be my first improvement.
Upvotes: 0
Reputation: 10421
import React, { Component } from 'react';
import styles from "./index.css";
import Logo from "./components/Logo";
import Navbar from "./components/Logo";
class Comp extends Component {
render(){
return (
<Header className={styles.root}
top={Logo}
right={Navbar}
/>
);
}
}
class Header extends Component {
render(){
let {top,right} =this.props;
return (
<header className={styles.root}>
<div className={styles.logo}>
{top && <top />}
</div>
<div>
{right && <right />}
</div>
</header>
);
}
}
Upvotes: 39
Reputation: 21
You can treat props as children, because technically children
is just another prop. There's nothing wrong with this approach – React contributors themselves suggest you do that (Support multiple child insertion points).
In addition, if you're working in a big team of engineers, I would also suggest standardizing how you name those props as well as their standard behavior – are they just plain content or a callback you can pass arguments to. react-placeholders can help you with that. So, the code of your component can look like that:
// header.jsx
import React from 'react';
import withSlots from 'react-placeholders';
@withSlots('logo', 'navigation')
export default class Header extends React.Component {
render() {
return (
<header className={styles.root}>
<div className={styles.logo}>
{this.props.header()}
</div>
<div>
{this.props.navigation('home')}
</div>
</header>
);
}
}
And this is how you'd embed that component
// app.jsx
import Header from 'header.jsx'
export default class Header extends React.Component {
render() {
return (
<Header
logoSlot={<img src="logo.svg" />}
navigationSlot={(currentPage) => {
return <a href="#" style={{ color: currentPage ? 'blue' : 'red' }}>Home</a>
}}
/>
);
}
}
Upvotes: 2