Is there a way to render the children of a React component under different divs?
<Page>
<Header> ... </Header>
<Content> ... </Content>
<Actions> ... </Actions>
</Page>
<div class="page">
<div class="header-wrapper>
<div class="header"> ... </div>
</div>
<div class="content-wrapper">
<div class="content"> ... </div>
<div class="actions"> ... </div>
</div>
</div>
Here I need the wrappers because the header is laid out differently from the area where content and actions are.
The obvious solution may be to introduce a Body component, but is there a more elegant way to abstract the concrete div tree which is needed to represent a certain layout from the logical components (Header, Content, Actions) which the Page declares.
Another solution which works is to pass the Header, Content, and Actions as properties as suggested in the React documentation:
<Page
header={
<Header> ... </Header>
}
content={
<Content> ... </Content>
}
actions={
<Actions> ... </Actions>
}
/>
Thanks,
George
React lets you access individual Children
<Page>
<Header> ... </Header>
<Content> ... </Content>
<Actions> ... </Actions>
</Page>
Inside <Page /> Component render
render() {
// when multiple elements are passed children prop is an array.
// extract each of the element passed from the array and place them
// wherever needed in the JSX
const [HeaderView, ContentView, ActionsView] = this.props.children;
<div class="page">
<div class="header-wrapper>
{HeaderView}
</div>
<div class="content-wrapper">
{ContentView}
{ActionsView}
</div>
</div>
}
Use React.Children.toArray to convert your props.children (that is an opaque data structure) to an array and safely reorganize them.
After convert to array, you can use find or forEach methods to get each one - Header, Content and Action - by testing the type of the item.
const children = React.Children.toArray(this.props.children);
const headerView = children.find(child => child.type.name === 'HeaderView');
const contentView = children.find(child => child.type.name === 'ContentView');
const actionsView = children.find(child => child.type.name === 'ActionsView');
<div class="page">
<div class="header-wrapper>
{headerView}
</div>
<div class="content-wrapper">
{contentView}
{actionsView}
</div>
</div>
Remember to declare HeaderView, ContentView and ActionsView with class or function declaration, not anonymous functions like:
const HeaderView = props => {
// some stateless rendering
};
Otherwise they might have no type.name to test against.
In Aurelia, you can use named slots to achieve this result, but in react the best workaround I have found is to do this:
// ...
const header = <Header someProp={someVal} />;
const content = <Content someOtherProp={someOtherVal} />;
const actions = <Actions action1={someAction} action2={someOtherAction} />;
return (
<Page header={header} content={content} actions={actions} />
);
and in page.jsx:
import React from 'react';
import PropTypes from 'prop-types';
export const Page = ({ header, content, actions }) => (
<div className="page">
<div className="header-wrapper">
<div className="header">
{header}
</div>
</div>
<div className="content-wrapper">
<div className="content">
{content}
</div>
<div className="actions">
{actions}
</div>
</div>
</div>
);
Page.propTypes = {
header: PropTypes.node.isRequired,
content: PropTypes.node.isRequired,
actions: PropTypes.node.isRequired,
};
Related
Hello i have a problem with react-router-dom i set up this route in App.js ```
and i want for eaxh element from my map to go to that Singleitem component and take all the params that elements have this elements is from an api i let down my code ```
<div className="wrapper">
{this.context.dailyShop.map((item, idx) => (
<div key={idx} className="itemCard">
<div className="itemtest">
<p className="textName text">{item.item.name}</p>
<p className="textCost text">{item.store.cost}</p>
<Link to={"/single/:" + item.item.name}>
<img className="itemPhoto" src={item.item.images.background} alt="alt" />
</Link>
</div>
</div>
))}
</div>
and here is the singlepage component ```import React from 'react';
const Singleitem = ({ match }) => {
console.log(match)
return (
<div>
hello from single page item {match.params.name}
</div>
)
}
export default Singleitem ```
i only get in that component the name and i want more props like id and more
I am new at React. Will be glad if someone can help:
I have parent (Dashboard) which contains all data. This data is passed to the children component (OnBoardingCard).
How can I render n times the OnBoardingCard component based on the data in the object at Dashboard without using the [num](in this case 3 times - 3x OnBoarding Cards;)?
Thank you!!
Parent- Dashboard
const cardData = [
{
svg: icon1,
title: 'Add',
content: 'add more'},
{
svg: icon2,
title: 'remove',
content: 'remove'
},
{
svg: icon3,
title: 'move',
content: 'move down'
}];
class Dashboard extends Component {
render() {
return (
<Section>
<OnboardingCard listData={cardData}/>
</Section>
);
} }
Children- OnBoardingCard
import Dashboard from "../../../../screens/Dashboard/index.js";
class OnboardingCard extends Component {
render() {
return (
<div className={styles.cardHolder}>
<div className={styles.fullCard}>
<div className={styles.onboardingCard}>
<div className={styles.iconBackground}>
<img src={this.props.listData[0].svg} />
</div>
<div className={styles.title}>{this.props.listData[0].title}</div>
</div>
<p className={styles.cardDescription}>
{this.props.listData[0].content}
</p>
</div>
</div>
); }}
When you are using a map inside render assign a unique key to its child component.
render(){
return(
{this.props.listData.map((item, i) =>
<div className={styles.cardHolder} key={i}>
<div className={styles.fullCard}>
<div className={styles.onboardingCard}>
<div className={styles.iconBackground}>
<img src={this.props.listData[0].svg} />
</div>
<div className={styles.title}>{this.props.listData[0].title}</div>
</div>
<p className={styles.cardDescription}>
{this.props.listData[0].content}
</p>
</div>
</div>
)}
);
}
You can use map function,
like this,
{this.props.listData.map((item)=>
<div className={styles.cardHolder}>
<div className={styles.fullCard}>
<div className={styles.onboardingCard}>
<div className={styles.iconBackground}>
<img src={item.svg} />
</div>
<div className={styles.title}>{item.title}</div>
</div>
<p className={styles.cardDescription}>
{item.content}
</p>
</div>
</div>)}
<Section>
<div className={styles.cardRow}>
{cardData.map((card, i) => (
<OnboardingCard {...card} key={i} />
))}
</div>
</Section>
This is what I meant (and wanted to do). So this solves my question. Thanks everyone!!
I recently moved my static navigation bar from being a general html element to included in my React rendering for my page because I wanted to incorporate the ability to dynamically load notifications in a modal that can get triggered in the navigation. With this change, I have noticed that my navigation bar does not appear immediately when the page is loaded, but when componentDidMount() { this.fetchList(); } finishes loading.
I personally belief that this is because the navigation component is being set in the render() call involved with this API fetch and since this class is being set after the call is made, then the navigation will have to wait until the fetch comes back successfully or as a failure.
If this is true, does that mean that I need to set my navigation at a higher level to ensure it loads when the page loads with styling and non-react elements?
Here is my ReactDOM.render():
import React from 'react';
import ReactDOM from 'react-dom';
import AnnotationFeedContainer from './components/app/activity-feed/activity-feed.js';
ReactDOM.render(<AnnotationFeedContainer />, document.getElementById('annotation-card'));
Here is <AnnotationFeedContainer /> which is rendering my react elements (<Navigation /> is the component I am looking to load before and regardless of fetchList()):
import React from 'react';
import fetch from 'node-fetch';
import path from 'path';
import Navigation from '../navigation';
import AnnotationSearchForm from './annotation-search-form';
import OnboardingInformation from './onboarding/information';
import ActivityFeedNotifications from './notifications/notifications';
import AnnotationFeed from './annotation-card/annotation-card-feed';
import { API_ROOT } from '../config/api-config';
//GET /api/test and set to state
export default class AnnotationFeedContainer extends React.Component{
constructor(props, context) {
super(props, context);
this.state = this.context.data || window.__INITIAL_STATE__ || { annotations: [], isLoading: true, onboardingWelcome: false, notifications: [] };
}
fetchList() {
fetch(`${API_ROOT}` + '/api' + window.location.search, { compress: false })
.then(res => {
return res.json();
})
.then(data => {
console.log(data);
this.setState({ annotations: data.annotation, user: data.user, csrf: data.csrfToken, isLoading: false, onboardingWelcome: data.onboardingWelcome, notifications: data.notifications, feedPreference: data.feedPreference });
})
.catch(err => {
console.log(err);
});
}
componentDidMount() {
this.fetchList();
}
render() {
if(this.state.feedPreference === 1){
return (
<div>
<Navigation notifications={this.state.notifications}/>
<AnnotationSearchForm />
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome}/>
<LoadingIndicator loading={this.state.isLoading} />
<div className="row">
<div className="col-md-12">
<AnnotationFeed {...this.state} />
</div>
</div>
</div>
</div>
</div>
)
} else {
return (
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome}/>
<LoadingIndicator loading={this.state.isLoading} />
<div className="row">
<div className="col-md-6 col-md-offset-3">
<AnnotationFeed {...this.state} />
</div>
<div className="col-md-1 col-md-offset-1">
<ActivityFeedNotifications notifications={this.state.notifications} />
</div>
</div>
</div>
</div>
)
}
}
};
//Loading Indicator
const LoadingIndicator = props => {
if(props.loading == true){
return (
<div className="spinner">
<div className="bounce1"></div>
<div className="bounce2"></div>
<div className="bounce3"></div>
<p>Loading...</p>
</div>
)
} else {
return null;
}
}
Navigation Component:
import React from 'react';
import NotificationPopover from './activity-feed/notifications/notifications-popover';
//Navigation
export default class Navigation extends React.Component {
render() {
return (
<nav className="navbar">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#navigationLinks">
<span className="icon-bar mobile-nav-toggle"></span>
<span className="icon-bar mobile-nav-toggle"></span>
<span className="icon-bar mobile-nav-toggle"></span>
</button>
<a className="navbar-brand" href="/app"><img src="/images/synotate_logo.svg" className="nav-logo-svg"></img></a>
</div>
<div className="collapse navbar-collapse" id="navigationLinks">
<ul className="nav navbar-nav">
<li className="nav-item">
<a className="nav-link" href="/app">Activity Feed</a>
</li>
<li className="nav-item">
<a className="nav-link" href="/app/settings">Settings</a>
</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li className="nav-item">
<NotificationPopover notifications={this.props.notifications}/>
</li>
<li className="nav-item">
<a className="nav-link" href="/app/logout">Log Out</a>
</li>
</ul>
</div>
</div>
</nav>
)
}
}
in your <AnnotationFeedContainer> you have this line if(this.state.feedPreference === 1) in the render() method.
For that condition wont be true unless you have a successful fetch() event coming from fetchList() in componentDidMount(), and if this condition returns true, you will render what ever is inside the braces of it, which includes the <Navigation> component
Else-wise you will render another code, which is loading indicator I guess, and in here, you didnt include your Navigation component, thus it won't show.
That is the logic you are using, you are telling your app to not include the Navigation component unless it fetches ur data, which happens to be logically fine!
if you want to display it other wise, you may wanna be moving it out of the if statement you have
It seems like you fetch call is responsible for setting the feedPreference variable in your state.
Since this variable is used in your if condition, and the <Navigation/> component isnt rendered when the feedPreference isn't set.
A simple solution would be to add <Navigation/> into the else condition (in the <AnnotationFeedContainer/>'s render function) :
} else {
return (
<>
<Navigation notifications={this.state.notifications}/>
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome}/>
<LoadingIndicator loading={this.state.isLoading} />
<div className="row">
<div className="col-md-6 col-md-offset-3">
<AnnotationFeed {...this.state} />
</div>
<div className="col-md-1 col-md-offset-1">
<ActivityFeedNotifications notifications={this.state.notifications} />
</div>
</div>
</div>
</div>
</>
)
}
A more "React-like" way of doing it could be to replace your entire condition wiht the following :
return (
<>
<Navigation notifications={this.state.notifications} />
{this.state.feedPreference === 1 && <AnnotationSearchForm />}
<div className="activity-feed-container">
<div className="container">
<OnboardingInformation onboarding={this.state.onboardingWelcome} />
<LoadingIndicator loading={this.state.isLoading} />
<div className="row">
{this.state.feedPreference === 1 ?
<>
<div className="col-md-6 col-md-offset-3">
<AnnotationFeed {...this.state} />
</div>
<div className="col-md-1 col-md-offset-1">
<ActivityFeedNotifications notifications={this.state.notifications} />
</div>
</>
:
<div className="col-md-12">
<AnnotationFeed {...this.state} />
</div>
}
<div className="col-md-6 col-md-offset-3">
<AnnotationFeed {...this.state} />
</div>
<div className="col-md-1 col-md-offset-1">
<ActivityFeedNotifications notifications={this.state.notifications} />
</div>
</div>
</div>
</div>
</>
)
Using inline ifs (&&) allows you to avoid repetitions. If you cannot use fragments (<> they were added in the latest React version) you can replace them with <div> tags
I am using React JSX. I have a div with className="shadow" as shown below.
<div className="main">
<div className="shadow" style={{backgroundColor: "#FFFFFF"}}>
<div id="wrapper">
Hello
</div>
</div>
</div>
Based on a certain condition being true or false, I want to remove the div with className="shadow", but want to keep every div including the div with id="wrapper" intact. Something like unwrap() method of jQuery.
Something to the effect of what is written below, but without so many lines of code.
if ( currentPage==="login") {
<div className="main">
<div id="wrapper">
Hello
</div>
</div>
}
else {
<div className="main">
<div className="shadow" style={{backgroundColor: "#FFFFFF"}}>
<div id="wrapper">
Hello
</div>
</div>
</div>
}
I checked React.js: Wrapping one component into another and How to pass in a react component into another react component to transclude the first component's content?, but didn't get what I am looking for.
Maybe what can help you is to change the className based on the page you are on, because what you try to do would be better using react-router to display different components based on the path you are on.
Use something similar to this code, I hope I can help you.
const App = React.createClass({
changePage(nextPage) {
this.setState({ page: nextPage })
},
getInitialState() {
return({
page: 'Login'
})
},
render() {
return(
<div className="main">
<div className={ this.state.page === 'Login' ? 'shadow' : '' }>
<div id="wrapper">
Hello from { this.state.page } page.
</div>
<button onClick={ this.changePage.bind(null, 'Login') }>Go to Login page.</button>
<button onClick={ this.changePage.bind(null, 'Home') }>Go to Home page.</button>
</div>
</div>
)
}
})
ReactDOM.render(<App />, document.getElementById('app'))
div.shadow{ background-color: #000000; color: #ffffff}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'></div>
This is a great use case for Higher Order Components!
const HOC = (currentPage, Shadow, Wrapper) => (
() => <div className="main">
{
currentPage === 'login'
? <Shadow {...this.props} />
: <Shadow {...this.props}><Wrapper {...this.props}>{this.props.children}</Wrapper></Shadow>
}
</div>
)
Usage:
render () {
const Shadow = props => <div className="shadow" style={{backgroundColor: '#FFFFFF'}}>{props.children}</div>
const Wrapper = props => <div id="wrapper">Hello</div>
const Example = HOC(
currentPage,
Shadow,
Wrapper
)
return (
<Example />
)
}
Update:
To render the children of <Wrapper />, use {this.props.children} and use the class syntax in HOC:
const HOC = (currentPage, Shadow, Wrapper) => (
class extends Component {
render () {
return (
<div className="main">
{
currentPage === 'login'
? <Shadow />
: <Shadow><Wrapper>{this.props.children}</Wrapper></Shadow>
}
</div>
)
}
}
)
If you needed to capture props on <Shadow /> and <Wrapper />, then do something like the following. Note: I don't think you can pass props into normal DOM elements like <div> tags. But if they were other components with a Capital starting letter, then I believe passing props through with {...this.props} would work nicely.
const HOC = (currentPage, Shadow, Wrapper) => (
class extends Component {
render () {
return (
<div className="main">
{
currentPage === 'login'
? <Shadow {...this.props} />
: <Shadow {...this.props}><Wrapper {...this.props}>{this.props.children}</Wrapper></Shadow>
}
</div>
)
}
}
)
Situation:
<Component>
child
</Component>
What I want to do:
<Component>
<div></div>
{this.function()} // loads array of components
</Component>
Problem:
I can only specify one propType? How can I allow two different propTypes into one?
Thanks!
I believe what you're trying to do should be fine. See this code from the Meteor react tutorial:
renderTasks() {
return this.getTasks().map((task) => (
<Task key={task._id} task={task} />
));
}
render() {
return (
<div className="container">
<header>
<h1>Todo List</h1>
</header>
<ul>
{this.renderTasks()}
</ul>
</div>
);
}
You could just as easily modify render() to be:
render() {
return (
<div className="container">
<header>
<h1>Todo List</h1>
</header>
<ul>
<Task task={this.props.myTask} />
{this.renderTasks()}
</ul>
</div>
);
}
Edit: in reply to your comment - yes you can specify alternate PropTypes as children. See this other answer:
static propTypes = {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.node),
React.PropTypes.node
]).isRequired
}