(For People That Already Know React)
JSX is just an abstraction over function calls. In much the same way that this:
const foo = {...bar};
Gets transpiled to this:
(function () {
var foo = Object.assign({}, bar);
}());
When you write JSX markup, it also gets converted to other code, so that this:
const foo = <Foo />;
Gets transpiled to this:
(function () {
var foo = React.createElement(Foo, null);
}());
This means that JSX is nothing more than a set of nested function calls, and any patterns that can be employed with standard function calls can also be employed within JSX layouts.
The 'children' prop is special within React development, in that it's typically used for passing subcomponents to a component for rendering. You don't have to pass subcomponent information this way, though, and it's not even required to be React elements. The only requirement is that a component returns React elements, a string, false, or null.
render () {
return (
<div>Hello World!</div>
);
}
render () {
return (
<div children="Hello World!" />
);
}
render () {
const props = {
children: "Hello World",
};
return (
<div {...props} />
);
}
render () {
const { name } = this.props;
return (
<Helloenator>
{({ greeting }) => `${greeting} ${name}!`}
</Helloenator>
);
}
Remember that component chunks in JSX only need to return React elements, strings, false, or null. This is why we're able to do things like this:
render () {
return (
{this.props.someFlag && (
<div>Hello World!</div>
)}
);
}
Remember that component chunks in JSX only need to return React elements, strings, false, or null. This is why we're able to do things like this:
render () {
return (
{this.props.someFlag && (
<div>Hello World!</div>
)}
); // returns false when !someFlag
}
It's also the reason we're able to do ternary operations like this:
render () {
return (
{this.props.someFlag ? (
<div>Hello World!</div>
) : null}
); // returns null when !someFlag
}
And it's also the reason we're able to do what are called render callbacks, like this:
render () {
const { name } = this.props;
return (
<Helloenator>
{({ greeting }) => `${greeting} ${name}!`}
</Helloenator>
); // returns the computed string
}
Components have to be written with awareness of render callback functions, like this:
render () {
return (
<div>
{this.props.children({
greeting: 'Hello'
})}
</div>
);
}
You don't even need to use 'children' at all, you can use any prop in exactly the same way:
render () {
return (
<div>
{this.props.render({
greeting: 'Hello'
})}
</div>
);
}
And then consume your component with a self-closing tag, since you don't need children:
render () {
const { name } = this.props;
return (
<Helloenator render={({ greeting }) => (
`${greeting} ${name}`
)} />
);
}
Since we can simply return null from any React component, that makes them great for creating declarative wrappers around tradition imperative code implementations.
This is the reason the bv-loader-internal-components repo has components subdivided into Analytics, Service, and UI component directories.
const LocalStorage = class extends React.Component {
_getItem () {
return localStorage.getItem(this.props.name);
}
render () {
let value;
try {
value = this._getItem()
} catch (error) {
value = error;
}
return (value instanceof Error ? null : this.props.children({ value }));
}
}
import Helloenator from './helloenator';
import LocalStorage from './local-storage';
const LocalStorageDrivenView = class extends React.Component {
render () {
return (
<LocalStorage name="FirstName}>
{({ value }) => (
<Helloenator>
{({ greeting }) => `${greeting} ${value}`}
</Helloenator>
)}
</LocalStorage>
);
}
}
Since JSX is just nested function calls, you can maybe better envision the previous example in functional terms, like this:
const localStorageDrivenView = name => {
const value = localStorage(name);
const greeting = helloenator();
return `${greeting} ${value}`;
};
If you find yourself performing the same set of operations across multiple components, you may have opportunity to pull out that repeated functionality, and create a higher-order component that can do those operations and apply them to your component in the form of props.
A higher-order component can be thought of as a curried (or partial) function, which is just waiting for you to provide the rest of its parameters.
const withContext = ctx => Component => {
const ContextWrapperComponent = class extends React.Component {
static contextTypes = ctx;
render () {
return (
<Component _ctx={this.context} {...this.props} />
);
}
};
return ContextWrapperComponent;
};
const ContextualComponent = ({ _ctx: { bar }, foo }) => (
<div>
<div>Foo from props = ${foo}</div>
<div>Bar from context = ${bar}</div>
</div>
);
export default withContext({
bar: propTypes.string,
})(ContextualComponent);
const withContext = ctx => Component => {
const context = getContextValues(ctx);
return ContextWrapperComponent({
children: Component({
_ctx: context,
...this.props,
}),
});
};
AND SO CAN YOU!