ReactJS layout component with dynamic child components - javascript

Using React 0.12.2 and given a layout component, e.g. a tray:
<div className="tray">
<div className="tray__item tray__item--left" data-width="260px">
Load a component in the left tray
</div>
<div className="tray__item tray__item--center">
Load a component in the center tray
</div>
<div className="tray__item tray__item--right" data-width="100%">
Load a component in the right tray
</div>
</div>
I would like to be able to insert arbitrary components into each of the contents, passing them as args to this component.
Perhaps something like:
<Tray left={Component1} center={Component2} right={Component3}/>
I would also like to know how to pass an unknown amount of components e.g:
<Carousel items={Component1,Component2,Component3,Component4}/>
Just to be clear - these container components are "dumb" - they only care about sliding content - you should be able to pass whatever content (components) you want to them.
How can I do that and then render them? Thanks.

In the render method of Tray you can do
render: function() {
return (
<div className="tray">
{this.props.children}
</div>
);
}
Then in the component where your Tray lives you can do
<Tray>
<TrayItem position="left"/>
<TrayItem position="center"/>
<TrayItem position="right"/>
</Tray>
You should be able to keep nesting this pattern, i.e.
<Tray>
<TrayItem position="left">
<SomeComponent/>
</TrayItem>
<TrayItem position="center">
<div>
<AnotherComponent/>
</div>
</TrayItem>
<TrayItem position="right"/>
</Tray>
In this case TrayItem's render should also include {this.props.children}
The general principle is, you can put arbitrary components inside other components as long as the container component's render includes {this.props.children}.

Thanks for the answer Adam Stone + SimpleJ.
var Tray = React.createClass({
render: function () {
return (
<div className="tray">
{this.props.children}
</div>
);
}
});
var TrayItem = React.createClass({
render: function () {
return (
<div className="tray__item">
{this.props.children}
</div>
);
}
});
<Tray>
<TrayItem>
<ComponentA/>
<ComponentAB/>
</TrayItem>
<TrayItem>
<ComponentB/>
</TrayItem>
<TrayItem>
<ComponentC/>
</TrayItem>
</Tray>

You should just create a container component that has multiple child components in its render function. You never want to pass a component in as a prop

Related

Render before or after child element

How do I render before or after a child element in a container?
I am learning React by integrating it into my own website. I started with this:
function createErrorSection(name, title, description) {
const section = document.createElement('section');
const container = document.createElement('div');
const h2 = document.createElement('h2');
const p = document.createElement('p');
section.appendChild(container);
container.appendChild(h2);
container.appendChild(p);
section.id = name;
section.classList = 'section-error';
container.classList = 'section-error-container';
h2.textContent = title;
p.textContent = description;
return section;
}
Which I turned into this:
function createErrorSection(name, title, description) {
return (
<section id={name} className='section-error'>
<div className='section-error-container'>
<h2>{title}</h2>
<p>{description}</p>
</div>
</section>
);
}
This is eventually propagated down to either node.before(section) or node.after(section).
I checked inside ReactDOM, ReactDOM/server and React with no luck. I saw I could create an HTML string, but I need an HTMLElement and would rather not do my own rendering if it can be avoided (I want to learn the React way, I already know the vanilla way).
My end goal is to learn how and when to use React properly. I'd love to know the proper way, but insight, advice and workarounds are also greatly appreciated!
In React you rather want to create a custom component with a single argument which contains the corresponding properties:
// single argument contains all props
function ErrorSection({name, title, description}) {
return (
<section id={name} className='section-error'>
<div className='section-error-container'>
<h2>{title}</h2>
<p>{description}</p>
</div>
</section>
);
}
now you need to import ReactDOM and call render in order to show the component ErrorSecion with some specific property values inside a HTML node with the id #app. Make sure that your HTML document contains such a node.
import ReactDOM from "react-dom";
ReactDOM.render(
<ErrorSection name="..." title="..." description="..." />,
document.querySelector("#app")
);
Most of the react apps render some dynamically generated nested components into the DOM using a single empty HTML node inside the document body (e.g. div#app or div#root). So you most likely will only need to have a single ReactDOM.render call in your entire project.
First of all, component's name should be written in PascalCase.
In React, you should rethink the way you render elements.
There are different approaches for different purposes:
Pass components to the children prop
const Wrapper = ({ children }) => (
<div className="wrapper">
<h1>Wrapper heading</h1>
{children}
</div>
);
Now you can pass children to the wrapper this way:
const AnotherComponent = () => (
<Wrapper>
<div>Element that will be rendered where the children prop is placed</div>.
</Wrapper>
);
Pass components to custom props:
If you need to render many components in different spots, you can do this:
const MultiSpotComponent = ({ HeaderComponent, FooterComponent }) => (
<div>
{HeaderComponent}
<div>Some content</div>
{FooterComponent}
</div>
);
And then pass your components to the props the same way you do with attributes in HTML:
<MultiSpotComponent HeaderComponent={CustomHeader} FooterComponent={CustomFooter} />
Notice that I used self-closing tag for the component, because I don't render children inside it.
Render list
const AnotherComponent = () => {
const dynamicArray = ['some', 'dynamic', 'values'];
return (
<div>
{dynamicArray.map(value => <div key={value}>{value}</div>)}
</div>
);
};
I have described only 3 most-used approaches, but there are more ways to render elements. You can learn more at Official React Documentation

What's the correct pattern to change parent state inside a child state in React?

Imagine I have a page "Parent" which conditionally renders a div "Child".
On the click of a button, "Child" opens. To close "Child" one has to click in a X button inside it.
This is how I would do it and in my opinion it looks clean.
const Parent = (props) => {
const [childVisible, setChildVisible] = useState(false);
return (
<>
{childVisible && <Child close={setChildVisible.bind(false)} />}
<button onClick={setChildVisible.bind(true)}>
Open Child
</button>
</>
)
}
const Child = (props) => {
return (
<div>
<p>Im Child</p>
<button onClick={props.close()}> X </button>
</div>
)
}
Since react v16.13.0 react has introduced a warning Warning: Cannot update a component from inside the function body of a different component. and it seems I can't do this anymore.
What's the correct pattern now? I would rather not have a state in both components stating the same thing.
Call back was not properly added .You could do like this onClick={props.close}
While use onClick={props.close()} like this. close() function run on child mount instead of click event
const Child = (props) => {
return (
<div>
<p>Im Child</p>
<button onClick={props.close}> X </button>
</div>
)
}

Carousel of components in React?

I'm building a website with React and for the "news" section i have a list of 3 components representing the different news. They belong to a flexbox and I'm trying to make them responsive. For mobile devices i would want only one of the 3 components to be shown with the ability of clicking 2 arrows to go through the news. Like a normal image carousel, but made of components. How can I achieve this? These are the components
The code where i put all the "news"
render() {
let content = <div>Loading...</div>;
if (!this.state.isLoading) {
content = (
<Aux>
<New
image={img1}
title={this.state.news[0].title}
text={this.state.news[0].text}
date={this.state.news[0].date}
classes="new-1"
/>
<New
image={img2}
title={this.state.news[1].title}
text={this.state.news[1].text}
date={this.state.news[1].date}
classes="new-2"
/>
<New
image={img3}
title={this.state.news[2].title}
text={this.state.news[2].text}
date={this.state.news[2].date}
/>
</Aux>
);
}
return content;
}
This is the "new" component
const New = props => {
const imageStyle = {
backgroundImage: `url(${props.image})`
};
return (
<div className={`new-wrapper ${props.classes}`}>
<div className="new-image" style={imageStyle}></div>
<div className="new-content">
<h4>{props.title}</h4>
<div className="text-wrapper">
<p>{props.text}</p>
</div>
<div className="date">
<span>{props.date}</span>
</div>
</div>
</div>
);
};
To achieve your desired result I would use a carousel plugin like https://react-slick.neostack.com/
You could set it to show three slides on desktop and just one on mobile so then you would get the arrows to go through the news cards.
I would also loop every element of the array by using the map function to render all the news. That way it would dynamically create a news card for every element on your state or array. See this example How to render an array of objects in React?
Hope this helps!

Why does my React grandchild component not update after receiving a change in props from its parent? Or: do I need to use state?

I have tried finding the answer to this on StackOverflow and there are some related posts (e.g. React Child Component Not Updating After Parent State Change) but I want to understand why this is not working...
I have a React application that will display a layout of character cards (that is, each card displays a different character). It uses a child component, CharacterBoard, that lays out the CharacterCards, which would be a grandchild component. I pass the characters down from the App to the CharacterBoard as props, and CharacterBoard in turn maps these out the CharacterCards.
The problem is that I want the state of the character to change when I click on one of them. Specifically, I want the revealed field to change. However, even though the state change is reflected in the array of characters in the App (that is, the revealed field changes correctly), and the change is reflected in the array of characters in CharacterBoard, but not in CharacterCard. In fact, my mapping does not seem to be called at all in CharacterBoard when the props change.
Do I need to use something like getDerivedStateFromProps in CharacterBoard and set the state of that component and then use the state to map the values down to CharacterCard? If so, why?
In short (tl;dr), can you pass props on down through the component chain and map them out along the way and still have all changes reflected automatically?
Thanks for any guidance.
If it helps, the render method of my App is
render() {
const {state: {characters}} = this
return (
<div>
<header>
</header>
<main>
<CharacterBoard
onCardSelected={this.onCardSelected}
rowSize={logic.ROW_SIZE}
characters={characters}
cardSize={this.CARD_SIZE}/>
</main>
</div>
);
}
that of CharacterBoard is
render() {
const {props: {characters, rowSize, cardSize,onCardSelected}} = this
const rowUnit = 12 / rowSize
const cardLayout = characters
.map((character, i) => (
<Col xs={6} sm={rowUnit} key={character.name}>
<CharacterCard
onCardSelected = {onCardSelected}
key={i + Math.random()}
character={character}
cardSize={cardSize}
/>
</Col>
)
)
return (
<div>
<Container>
<Row>
{cardLayout}
</Row>
</Container>
</div>
)
}
and finally CharacterCard has this render method
render() {
const {props: {character, cardSize}} = this
const {thumbnail, revealed} = character
const imgURL = `${thumbnail.path}/${cardSize}.${thumbnail.extension}`
const topCardClass = classNames('characterCard__card-back', {'characterCard__card-back--hidden': revealed})
console.log(revealed)
return < a href="/#" onClick={this.onCardSelected}>
<div className='characterCard__card'>
<div className={topCardClass}>
<img src="/images/card_back.png" alt=""/>
</div>
< div className='characterCard__card-front'>< img alt=''
src={imgURL}/>
</div>
</div>
</a>
}
Doh! A simple forgetting to setState in App. Knowing that it should work made me go back through the code one more time and see that, indeed, it was a stupid error on my part.

React: Inject common render logic from parent

Let's assume i have a react component class that displays a modal dialog on a click of a button.
it can be created like this (in jsx):
<Modal text={"some text"}/>
Now, I have a bunch of component classes (let's call them Panels) that all have a function called getMessage, and i'd like the same behavior in all of these components: the modal dialog should show the string that returns from the call to getMessage.
the straight forward way to do this would be to include
<Modal text={this.getMessage()}/>
in the render() function for each such component.
Now, let's say that there is a bit more logic involved. for example, i would only like to render this component if getMessage is defined and does not return null.
Now this is starting to look like this:
var Panel1 = React.createClass({
getMessage: function() {return 'wow';},
render: function() {
var modal = null;
if (this.hasOwnProperty('getMessage' && this.getMessage() !== null) {
modal = <Modal text={this.getMessage()}/>
}
return (
<div>
{modal}
...all other stuff done in panel
</div>
);
}
});
This is starting to become cumbersome because I need to have this logic for each and every component class I define.
How can I achieve DRYness in this scenario so that i don't have to repeat this?
One way would be to define a utility function that contains this logic, let's call it displayModalIfNeeded and the call it from render. this now looks like this:
return (
<div>
{displayModalIfNeeded.call(this)}
....all other stuff needed in Panel
</div>
);
And now for my actual question (sorry for the long exposition):
Let's say that i have a parent component called <Dashboard> which has all panels as its childern:
<Dashboard>
<Panel1>
<Panel2>
<Panel3>
</Dashboard>
Is there something i can write in the implementation of Dashboard that will entirely remove the need to specify anything about these modal components in each Panel?
meaning the the Panel1 implementation can now just be
<div>
...all other stuff done in panel
</div>
and when it's rendered as a child of Dashboard it will have that modal dialog and accompanying logic.
I suggest using a wrapper component with the children prop. Your parent component would look like this:
<Dashboard>
<ModalWrapper text={msg1}>
<Panel1 />
</ModalWrapper>
<ModalWrapper text={msg2}>
<Panel2 />
</ModalWrapper>
<ModalWrapper text={msg3}>
<Panel3 />
</ModalWrapper>
</Dashboard>
Now all your conditional logic can be placed in ModalWrapper. Where your question has "....all other stuff needed in Panel", use this.props.children. e.g.
var ModalWrapper = React.createClass({
render: function () {
var text = this.props.text;
return (
<div>
{text ? <Modal text={text} /> : null}
{this.props.children}
</div>
);
}
});

Categories