Setting children in defaultProps - reactjs

I am developing a React component that contains some child components. I would like it to have a default set of children, if its element is created without specifying children (i.e. <MyContainerComponent ... /> should be equivalent to <MyContainerComponent ...><!-- some default child elements --></MyContainerComponent>).
From https://github.com/facebook/react/blob/v0.14.8/src/isomorphic/classic/element/ReactElement.js#L135, I understand that props.children will be undefined if no children are specified. Thus, I would like to rely on this and set children in the defaultProps of MyContainerComponent.
I am not sure, however, if I am coupling my decision too much with React's implementation (i.e. by design, as a user of React, is it acceptable to treat children just like any other prop that would be undefined if you did not explicitly define it). All other materials I read on children treat is as either a single element, or an array, but not potentially undefined.
I'd really appreciate a more experienced opinion.

The React project hasn't done a great job of documenting how props.children is treated, but I would say that:
it [is] acceptable to treat children just like any other prop that would be undefined if you did not explicitly define it
References:
Comments by Sebastian Markbåge:
The type of this.props.children is whatever your component's propTypes says it is.
And:
We recommend that most of the time you use the general ReactNode type for your this.props.children.
Where the definition of ReactNode includes undefined.
My interpretation is that undefined is a valid value for props.children and should be the default in general. But note that with JSX even whitespace might affect the value:
<MyContainerComponent> </MyContainerComponent>
All of this being said, as indicated by the linked GitHub issue I'm trying to get this better documented so the API contract is clear :)

Set default props for children using getDefaultProps. If react changes the definition of empty props === undefined, they will also do so in the getDefaultProps life cycle method (fiddle):
var Parent = React.createClass({
getDefaultProps: function() {
return {
children: <span>default child</span> // set children in default props
};
},
render: function() {
return <div>I am a { this.props.children }</div>; // if we have children use them, if not use default from state
}
});
ReactDOM.render(
<Parent />, // without children
document.getElementById('container1')
);
ReactDOM.render(
<Parent>non a default child</Parent>, // with children
document.getElementById('container2')
);

You can use React.createElement as default value for your children props
https://reactjs.org/docs/react-api.html#createelement
example:
import React, { PureComponent } from 'react'
import { element } from 'prop-types'
export class Example extends PureComponent {
static propTypes = { children: element }
static defaultProps = { children: React.createElement('div') }
render () {
const { children } = this.props
return <div className='parent'>{children}</div>
}
}

Related

Rendering a component as the parent of the actual component

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>);
}

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)
}

is there any way to access the parent component instance in React?

I know it's not a functional approach to be able to do something like this.parent in a React component, and I can't seem to find any properties on a React component instance that lead to the parent, but I'm just looking to be able to do some custom things where I need this.
Before anyone wastes their time explaining it's not the functional React "way," understand that I need this because of the following I'm trying to achieve:
Build a transpiler for Meteor's Spacebars templating engine, whose rendering model does take into consideration parent components/templates.
I've already built a transpiler that modifies the output jsx to achieve this. I do this by passing in parent={this} in all child components composed. However, after the fact it occurred to me that maybe I simply don't know of something that will give me a way to access the parent component instance without additional transpilation modifications.
Any tips would be much appreciated.
There's nothing wrong if you need to access the parent's props and functions from the children.
The point is that you should never use React internals and undocumented APIs.
First of all, they are likely to change (breaking your code) and, most importantly, there are many other approaches which are cleaner.
Passing props to children
class Parent extends React.Component {
constructor(props) {
super(props)
this.fn = this.fn.bind(this)
}
fn() {
console.log('parent')
}
render() {
return <Child fn={this.fn} />
}
}
const Child = ({ fn }) => <button onClick={fn}>Click me!</button>
Working example
Using context (if there's no direct parent/child relation)
class Parent extends React.Component {
constructor(props) {
super(props)
this.fn = this.fn.bind(this)
}
getChildContext() {
return {
fn: this.fn,
}
}
fn() {
console.log('parent')
}
render() {
return <Child fn={this.fn} />
}
}
Parent.childContextTypes = {
fn: React.PropTypes.func,
}
const Child = (props, { fn }) => <button onClick={fn}>Click me!</button>
Child.contextTypes = {
fn: React.PropTypes.func,
}
Working example
Update for React 0.13 and newer
Component._owner was deprecated in React 0.13, and _currentElement no longer exists as a key in this._reactInternalInstance. Therefore, using the solution below throws Uncaught TypeError: Cannot read property '_owner' of undefined.
The alternative is, as of React 16, this._reactInternalFiber._debugOwner.stateNode.
You've already recognized that this is not a good thing to do almost always, but I'm repeating it here for people that don't read the question very well: this is generally an improper way to get things done in React.
There's nothing in the public API that will allow you to get what you want. You may be able to get to this using the React internals, but because it's a private API it's liable to break at any time.
I repeat: you should almost certainly not use this in any sort of production code.
That said, you can get the internal instance of the current component using this. _reactInternalInstance. In there, you can get access to the element via the _currentElement property, and then the owner instance via _owner._instance.
Here's an example:
var Parent = React.createClass({
render() {
return <Child v="test" />;
},
doAThing() {
console.log("I'm the parent, doing a thing.", this.props.testing);
}
});
var Child = React.createClass({
render() {
return <button onClick={this.onClick}>{this.props.v}</button>
},
onClick() {
var parent = this._reactInternalInstance._currentElement._owner._instance;
console.log("parent:", parent);
parent.doAThing();
}
});
ReactDOM.render(<Parent testing={true} />, container);
And here's a working JSFiddle example: http://jsfiddle.net/BinaryMuse/j8uaq85e/
Tested with React 16
I was playing around with something similar using context, tho to anyone reading this, for most usual cases, accessing the parent is not advised!
I created a holder that when used, would always have a reference to the first holder up the display list, so its 'parent' if you will. Looked something like this:
const ParentContext = React.createContext(null);
// function to apply to your react component class
export default function createParentTracker(componentClass){
class Holder extends React.PureComponent {
refToInstance
render(){
return(
<ParentContext.Consumer>
{parent => {
console.log('I am:', this, ' my parent is:',parent ? parent.name : 'null');
return(
<ParentContext.Provider value={this}>
<componentClass ref={inst=>refToInstance=inst} parent={parent} {...this.props} />
</ParentContext.Provider>
)}
}
</ ParentContext.Consumer>
)
}
}
// return wrapped component to be exported in place of yours
return Holder;
}
Then to use it you would pass your react component to the method when you export it like so:
class MyComponent extends React.Component {
_doSomethingWithParent(){
console.log(this.props.parent); // holder
console.log(this.props.parent.refToInstance); // component
}
}
// export wrapped component instead of your own
export default createParentTracker(MyComponent);
This way any component exporting the function will get its parent's holder passed in as a prop (or null if nothing is further up the hierarchy). From there you can grab the refToInstance. It will be undefined until everything is mounted though.

React context doesn't transfer when using a component as prop

I'm using react-bootstrap's ModalTrigger to show a field-heavy modal (based on react-bootstrap's Modal), which means sending it a bunch of props:
<ModalTrigger modal={<MyModal field1={value1} field2={value2} (more fields...)/>}>
Click here to open
</ModalTrigger>
The parent component that creates the trigger has the fields/values passed in via props, and the parent component of that component has it passed as props as well, by the top-level component that actually holds the data. Both are basically pipes, which is a classic childContext scenario, except that it doesn't work. Here's a simplified version of what I've tried:
var MyModal = React.createClass({
contextTypes : {foo : React.PropTypes.string},
render : function() {
return (
<Modal {...this.props} title="MyTitle">
<div className="modal-body">
The context is {this.context.foo}
</div>
</Modal>
);
}
});
var Content = React.createClass({
childContextTypes : {foo: React.PropTypes.string},
getChildContext : function() {return {foo : "bar"}},
render : function() {
return (
<ModalTrigger modal={<MyModal/>}>
<span>Show modal</span>
</ModalTrigger>
)
}
});
The modal pops up with "The context is", without showing the actual context.
I believe this is happening because the prop sent to ModalTrigger is already rendered/mounted somehow, but I'm not sure why. To the best of my understanding, the owner of MyModal is the Content component, which means that the context should be ok, but this is not the case.
Some more information: I already tried passing {...this.props} and context={this.context} to MyModal with no success. Also, maybe relevant, ModalTrigger uses cloneElement to make sure the modal's onRequestHide prop points to the trigger's hide function.
So what am I missing here? :/
React.cloneElement will change the owner of the element when the ref prop is overriden, which means context will not be passed from the previous owner. However, this does not seem to be the case with ModalTrigger.
Note that the owner-based approach won't work altogether in React 0.14, since context will be passed from parent to child and not from owner to ownee anymore. ModalTrigger renders its modal node prop in another branch of the DOM (See OverlayMixin). Thus, your Modal component is not a child nor a descendant of your Content component and won't be passed child context from Content.
As for solving your problem, you can always create a component whose sole purpose is to pass context to its children.
var PassContext = React.createClass({
childContextTypes: {
foo: React.PropTypes.string
},
getChildContext: function() {
return this.props.context;
},
render: function() {
return <MyModal />;
},
});
To use it:
<ModalTrigger modal={<PassContext context={this.getChildContext()}/>}>
As Matt Smith hinted, it turns out that react-bootstrap already includes a very similar approach to forwarding context via ModalTrigger.withContext. This allows you to create a ModalTrigger component class which will forward its context to its modal node prop, no matter its position in the VDOM tree.
// MyModalTrigger.js
module.exports = ModalTrigger.withContext({
foo: React.PropTypes.String
});
There is a much better way of passing context on to your "portal" type components which render their children into a different container outside the React tree.
Using "renderSubtreeIntoContainer" rather than "render" will pass the context into the subtree as well.
It can be used like so:
import React, {PropTypes} from 'react';
import {
unstable_renderSubtreeIntoContainer as renderSubtreeIntoContainer,
unmountComponentAtNode
} from 'react-dom';
export default class extends React.Component {
static displayName = 'ReactPortal';
static propTypes = {
isRendered: PropTypes.bool,
children: PropTypes.node,
portalContainer: PropTypes.node
};
static defaultProps = {
isRendered: true
};
state = {
mountNode: null
};
componentDidMount() {
if (this.props.isRendered) {
this._renderPortal();
}
}
componentDidUpdate(prevProps) {
if (prevProps.isRendered && !this.props.isRendered ||
(prevProps.portalContainer !== this.props.portalContainer &&
prevProps.isRendered)) {
this._unrenderPortal();
}
if (this.props.isRendered) {
this._renderPortal();
}
}
componentWillUnmount() {
this._unrenderPortal();
}
_getMountNode = () => {
if (!this.state.mountNode) {
const portalContainer = this.props.portalContainer || document.body;
const mountNode = document.createElement('div');
portalContainer.appendChild(mountNode);
this.setState({
mountNode
});
return mountNode;
}
return this.state.mountNode;
};
_renderPortal = () => {
const mountNode = this._getMountNode();
renderSubtreeIntoContainer(
this,
(
<div>
{this.props.children}
</div>
),
mountNode,
);
};
_unrenderPortal = () => {
if (this.state.mountNode) {
unmountComponentAtNode(this.state.mountNode);
this.state.mountNode.parentElement.removeChild(this.state.mountNode);
this.setState({
mountNode: null
});
}
};
render() {
return null;
}
};
This is an example of a portal I use in my production app Casalova that render context properly into their children.
Note: this API is undocumented and is likely to change in the future. For now, though, it's the right way to render context into portal components.

Resources