Rendering a component as the parent of the actual component - reactjs

I have 2 components, I would like the first component to be a render prop in the second component, which will encapsulate the content of the second component if it is defined. I don't quite understand how to write this, and the documentation I find on render props tends to be difficult to understand. Has anyone written a similar implementation?
The general idea i'm after is that you pass in the props for component1 in the component1 prop for component2, and it renders <Component1> with it's props inside component2.
Rough code example of what i'm trying to do (It isn't meant to work)
interface Component1 {
id?: string;
children: React.ReactNode;
}
const Component1 = (props: Component1) => {
const { children } = props;
return (<div className="component1">{children}</div>)
}
interface Component2 {
component1?: (propsForComponent1) => <Component1 {...propsForComponent1}>
}
const Component2 = (props: Component2) => {
const {component1} = props;
if (component1) {
return {component1({id: 'exampleId', children: <div className="component2">Stuff for component 2</div>)}}
};
return (<div className="component2">Stuff for component 2</div>);
}
Edit: Have changed the example because the intention is confusing people.
Edit: Will just pass the first component as a prop into the second component for now. I think the general answer is to not try to use a component as two components, and just stick to children.

What is actually happening in your code sample, is that even if you are passing down a whole component (Component1) as a prop, you are not rendering it, but using an imported (or accessible inside the scope) Component1.
Anyways - why would you want to pass down a whole component as a prop, even if you can basically just import it?
What I would suggest - use a prop, but not a component, but a boolean flag that determines if the component should be wrapped or not.
interface Component2 {
shouldBeWrapped?: boolean;
}
const Component2 = ({ shouldBeWrapped }: Component2) => {
if (shouldBeWrapped) {
return (
<Component1 with props given in on component1 property>
<div className="component2">Stuff for component 2</div>
</Component1>
);
}
return (<div className="component2">Stuff for component 2</div>);
}

Related

React render specific children

I have looked through other peoples questions relating to this but cant find a suitable answer. I would like to pass children to a component and then pull out the specific children where I want them, most examples I have seen just have the children render in the same place.
My component looks something like this -
<ParentComponent>
<ChildOne/>
<ChildTwo/>
<ParentComponent/>
When I log the props.children inside the parent component I get an array which contains both children as objects. is there a simple way to pull out the specific child where I need it such as {props.children.ChildOne} at the moment I am using props.children[0] which isn't ideal as we will be passing the children dynamically
in the future and the array length may change.
As always any help is greatly appreciated!
Depending on your exact situation and needs, it might make more sense to pass child components as props than using the special children prop. Then you can render them whichever way you like.
<ParentComponent childOne={ChildOne} childTwo={ChildTwo} />
...
const ParentComponent = ({ childOne, childTwo }) => {
return (
<div>
{childOne}
<div>
{childTwo}
</div>
</div>
);
};
But knowing your exact scenario would help a lot with conceptualising the best way to implement this. Perhaps you can refactor your code to avoid passing an array of children like this.
Actually, the ReactChildren API I was mentioning is useless here.
You can do something like this instead:
import React from 'react';
import { ChildOne } from './YourFile';
export function ParentComponent({children}) {
return children.find(child => child.type === ChildOne)
}
You should define the displayName property for the child components and then use the displayName in the parent to find the specific children from children list and place them where you want it to be.
// define displayName for each component, it can be any string
// You can set the displayName for any react component before exporting as shown
// below
const ChildOne = (props) => { return (<div> Child One </div>)}
ChildOne.displayName = "ChildOne";
export default ChildOne;
const ChildTwo = (props) => { return (<div> Child Two </div>)}
ChildTwo.displayName = "ChildTwo";
export default ChildTwo;
Now in parent component you can filter out the specific child by using their displayName.
const ParentComponent = (props) => {
const getChildByDisplayName = (displayName) => {
const child = React.Children.map(props.children, (child => {
// you can access displayName property by child.type.displayName
if (child.type.displayName === displayName) return child;
return null;
}))
return child;
}
return (
<div>
{/* You can change the order here as per your wish*/}
{getChildByDisplayName("ChildOne")}
{getChildByDisplayName("ChildTwo")}
</div>
)
}
That's it, Now even if you put ChildTwo before ChildOne like below example, parent component will still render the ChildOne first and then ChildTwo because we have defined order in parent.
<ParentComponent>
<ChildTwo/>
<ChildOne/>
<ParentComponent/>
Using the key seems simpler:
whatever is using the parent component:
<ParentComponent>
<ChildOne key="title"/>
<ChildTwo key="picture"/>
<ParentComponent/>
parent component:
export default function ParentComponent(props: any) {
const title = props.children.find((o: any) => o.key === 'title')
const picture = props.children.find((o: any) => o.key === 'picture')
return <div>
<jumbobox>
{title}
</jumbobox>
<fancyframe>
{picture}
</fancyframe>
</div>
}
One way is to control the Child Components being passed to ParentComponent through state/props/redux store management in the component containing ParentComponent and its children.
But to have the solution as per your use case, we can avoid using children prop and use our defined prop on ParentComponent.
<ParentComponent
components={[
{key: 1, component: <div>Hello</div>}
]}
/>
So, we can now filter from the key.
Checkout this demo

How does this component render its children?

I am studying the code for react-accessible-accordion, and I don't understand how it renders its children.
From Accordion.tsx:
export default class Accordion extends React.Component<AccordionProps> {
// ... defaults
renderAccordion = (accordionContext: AccordionContext): JSX.Element => {
const {
preExpanded,
allowMultipleExpanded,
allowZeroExpanded,
onChange,
...rest
} = this.props;
return <div data-accordion-component="Accordion" {...rest} />;
};
render(): JSX.Element {
return (
<Provider
preExpanded={this.props.preExpanded}
allowMultipleExpanded={this.props.allowMultipleExpanded}
allowZeroExpanded={this.props.allowZeroExpanded}
onChange={this.props.onChange}
>
<Consumer>{this.renderAccordion}</Consumer>
</Provider>
);
}
}
This component accepts a few levels of nested children. Specifically, I don't understand how they are being passed down.
I can see that the component spreads the rest of props over a self-closing Accordion div element... How does that mechanism manage to render multiple levels of children?
A React context Consumer expects a function as its child to render the content. That function in this example is referenced as this.renderAccordion:
<Consumer>{this.renderAccordion}</Consumer>
Which renders the children in the {...rest} spread attributes:
const {
preExpanded,
allowMultipleExpanded,
allowZeroExpanded,
onChange,
...rest
} = this.props;
return <div data-accordion-component="Accordion" {...rest} />;
The ...rest includes children from this.props (and you can actually render children as an attribute, like <div children={ <p>Hello!</p> } />) from the destructuring assignment -- in other words const { ...rest } = this.props includes this.props.children.
There is Provider for providing and Consumer for rendering the child components because the props are spread to the Consumer and children is a prop of Accordian.
Here is the consumer being used
For individual components such as the AccordianItem, this uses Provider to define components which are meant to be rendered.
This may help you to understand.
Basically, when JSX is compiled to React code, it creates a component using:
React.createElement("div", null, children);, or
React.createElement("div", { children }, null);
Check how Hello, Foo and Bar component are compiled in the link that I sent you. Your case is gonna be the Bar component

How to type a component that is a child of a parent component that had its props dynamically added to?

I have two components -- one being a child and the other a parent. The parent component is designed to be a HOC that makes an ajax call for data, and then it passes that data down to its child. The props that it passes down to the children is dynamically added through React.CloneElement with the this.props.children.
With regards to flow, it isn't able to interpret/see the additional props I've added when re-cloning.
How do I get past this?
How both components relate.
<ParentComponent>
</ChildComponent />
</ParentComponent>
const ParentComponent = (props) => {
// code
return (
<>
{React.cloneElement(this.props.children, { extraProps }}
<>
);
}
type PropTypes = {
extraProps: string, // flow complains, and I don't know how to get it to recognize that the extraProps is added dynamically.
};
const ChildComponent = (props) => {
// code that uses the additional `extraProps`
}
So far I haven't found a good way to get it to work completely. Here's some code that will set it in the right direction though.
import * as React from 'react'
const ParentComponent = (props: { children: React.Element<typeof ChildComponent> }) => {
return (React.cloneElement(props.children, { extraProps: 'hello' }));
// return (React.cloneElement(props.children, { extraProps: 1 })); // <-- this will error
}
type PropTypes = {
extraProps?: string, // not great because you'll still have to check presence, but at least it provides
// type checking on extraProps in the ParentComponent
};
const ChildComponent = (props: PropTypes) => {
return <p>{props.extraProps}</p>
}
<ParentComponent><ChildComponent /></ParentComponent>
Flow Try
Flow has some great documentation on typing higher-order components. It changes your implementation slightly, but I think it should achieve the same effect as your code. Using the documentation's example for injecting props, you could write,
import * as React from 'react';
type Props = {
x: string,
y: string,
}
function injectY<Config: {}>(
Component: React.AbstractComponent<Config>
): React.AbstractComponent<$Diff<Config, { y: string | void }>> {
return function WrapperComponent(
props: $Diff<Config, { y: string | void }>
) {
return <Component {...props} y="injected" />;
}
}
function Child({ x, y }: Props) {
return x + y;
}
const EnhancedChild = injectY(Child);
function App() {
return (
<div>
<Child x="explicit" y="explicit" />
<EnhancedChild x="explicit" />
</div>
);
}
Try Flow
The injectY prop is generic right now so you can reuse that pattern for other places where you might be using React.cloneElement. I'm not familiar with React.cloneElement so I can't say much about the differences between the cloning and HOC approaches other than you'll probably get better type checking with injectY.

Joining React props with custom props

When defining props in React, using Typescript, it seems that the default React props get overwritten by whatever the interface is. Is there a clean way to merge the two without having to specify every prop React already knows about?
Example:
interface IProps { // extends React.???
title: string;
// Generally seen adding children?: any
// But this can get out of hand with onClick, onChange, etc
}
function MyComponent(props: IProps) {
return props.children; // Typescript error: children doesn't exist on props
}
What you're referring to as "React default props" aka "every prop React already knows about" are more properly called "props accepted by any React DOM element wrapper component", i.e. onClick, className, etc.
Default props typically refers to the static defaultProps property on a React.Component, with which you provide default values for any props that were not passed to your component.
onClick, className, etc. are not reserved prop names and you can use them however you want in your own components, for instance you could have your component expect className to be a function (regardless of whether it's a good idea). The only reserved prop names that work on React elements of any kind (at the time of writing) are key and ref, and they're not really true props because they're not available to your component's render method.
Passing onClick to your own component does not automatically register a click handler. It will only do so if you pass the onClick you received to a <div>, <button>, or other React DOM Element wrapper that you render somewhere down the line. If you don't do anything with a prop you were passed, it has no effect (besides possibly causing a pure render component to update when it otherwise wouldn't).
For example, the following component will not respond to clicks:
const ClickFail = props => <button />
render(<ClickFail onClick={...} />, document.getElementById('root'))
But the following will:
const ClickSuccess = props => <button onClick={props.onClick} />
render(<ClickSuccess onClick={...} />, document.getElementById('root'))
And you could pass onClick to only one subelement if you really wanted:
const ClickButtonOnly = props => (
<form>
<input placeholder="ignores clicks" />
<button onClick={props.onClick}>Handles Clicks</button>
</form>
)
Or you could pass in multiple click handlers with different names:
const SimpleForm = props => (
<form>
<button onClick={props.onCancelClick}>Cancel</button>
<button onClick={props.onOKClick}>OK</button>
</form>
)
Also keep in mind that some DOM element wrappers accept props that others do not, for instance readOnly applies only to <input> and <textarea>.
You can even require children to be whatever type you want. For instance, you can pass a function as the children of a component and use it (again, not the best use of React, but just to illustrate what's possible):
type Props = {
value: number,
children: (value: number) => number,
}
const ApplyFunction = (props: Props) => (
<div>{React.Children.only(props.children)(props.value)}</div>
)
render(
<ApplyFunction value={3}>
{value => value * value}
</ApplyFunction>,
document.getElementById('root')
)
// renders <div>9</div>
So you see, IProps does not necessarily have to extend anything.
However, it is common to pass along rest props to a React DOM Element wrapper (e.g. <div {...props}>...</div> and as you were asking, it would be nice to be able to check the type of all of those input properties to your component.
I think you could do the following with Flow to check the types correctly, but unfortunately I don't think there's any Typescript equivalent (someone correct me if I'm wrong):
type Props = React.ElementProps<typeof 'div'> & {
title: string,
}
const MyComponent = (props: Props) => (
<div {...props}>
{props.title}
</div>
)
You should define that your stateless functional component will return React.SFC<YourProps>.
Try this
import * as React from "react";
const MyComponent: React.SFC<IProps> = (props) => {
return props.children;
}
If you want to use class-based component, you can extend your class with React.Component<YourProps(optional), YourState(optional)> instead
For example
import * as React from "react"
class MyComponent extends React.Component<IProps> {
public render(): JSX.Element {
return (
<div>...</div>
);
}
}
type TitleProps = { // your custom props
level?: level;
color?: string;
};
const Title = (props: TitleProps & React.Component['props']) => { // join default props and custom props
const { level = 'h1', color, children } = props; // joined props containing default props
return <Text style={[styles[level], color && { color }]}>{children}</Text>;
}
I was able to solve the problem by this way.

React Pass all/multiple props to dynamic children - preferably without context

I've found several examples/posts about how to pass ONE prop to dynamic chidren using React such as:
return React.cloneElement({this.props.children}, { parentValue: this.props.parentValue });
However passing multiple or all props seems to send things into a recursion loop that eventually crashes the app:
return React.cloneElement({this.props.children}, { parentValue1: this.props.parentValue1, parentValue2: this.props.parentValue2});
or..
return React.cloneElement({this.props.children}, {...this.props});
Is there an effective way to pass multiple (or all) props to dynamic children? It would seem that if you can for one or more static children, you should be able to if they happen to be dynamic.
you can do it like this
import React from 'react'
export default class Container extends React.Component {
render() {
return (
<div>
{React.cloneElement(this.props.children, {...this.props})}
</div>
)
}
}
from now any element you put inside container will have container's props
<Container><ContactPage /></Container>
// contactPage will have all props from container
Don't forget to exclude children from props to pass it further.
I spent lots of hours to understand why I have a loop.
You can read more here about this issue
render() {
const { children, ...otherProps } = this.props
return React.cloneElement(this.props.children, otherProps)
}

Resources