Composables for React.
react-stamp
has replaced react-stampit and is compliant with the stamp specification. The Rtype specification is used for documenting function signatures and data structures.
npm install react-stamp --save
Composition is the act of creating an object from a collection of other objects. Many will say this is actually multiple inheritance, not composition. Well, in the classical sense of the word, they're right! However, JavaScript favors prototypal inheritance, and composition is actually Prototypal OO's primary mechanism. Composition encompasses differential inheritance, concatenative inheritance, and functional inheritance.
But I like HOCs.
So do I! HOC factories provide a functional API for component composition and stamp composition can be a nice complement. If the goal is to be functional and avoid APIs that expose class
and it's pseudo-classical behavior, why use class
at all?
No. The only similarity is that both provide forms of object composition. react-stamp
decouples the relationship between component and mixin while being less opinionated. This provides greater flexibility.
react-stamp
is the result of wondering about what other ways a React component could be represented. Stamps are a cool concept and more importantly have proven to be a great alternative to React.createClass
and the ES2015 class
due to their composability.
react-stamp
exports a function that accepts one parameter, the React library.
reactStamp(React?: Object) => Stamp
This method converts React's Component
constructor function into a stamp. To create a React component, we pass a descriptor object to the stamp's compose
method.
interface ReactDesc {
displayName?: String,
init?: Function,
state?: Object,
statics?: Object,
contextTypes?: Object,
childContextTypes?: Object,
propTypes?: Object,
defaultProps?: Object,
...methods?: Function
}
stamp.compose(...desc?: Stamp|ReactDesc|SpecDesc[]) => Stamp
The most powerful feature of stamps is their composability. Any number of stamps can be combined into a new stamp which inherits each passed stamp's behavior. This behavior is suitable for React since class
is being pushed as the new norm and does not provide an idiomatic way to utilize mixins. (classical inheritance 😞).
Let's step through an example. It demos a pattern I call Behavior Driven Composition.
container.js
container({
React: Object,
...components: Function[]
}, ...behaviors?: Descriptor[]) => Stamp
import reactStamp from 'react-stamp';
export default ({
React,
Button,
Text,
}, ...behaviors) => (
reactStamp(React).compose({
state: {
showText: false,
clickable: false
},
render () {
const { clickable, showText } = this.state;
const { text } = this.props;
return (
<div>
<Button
disabled={!clickable}
onClick={() => this.onClick && this.onClick()}
value='click me'
/>
<Text
value={showText && text}
/>
</div>
);
}
}, ...behaviors)
);
BDC uses the concept of a container. The container is a HOC factory that defines the structure of a smart component. This particular container expects two children components, Button
and Text
. The container should be self-sufficient and boring. Personality can be added by inheriting behaviors. Keep reading to see an example.
button.js
export default React => (
({ disabled, onClick, value }) => (
<input
type='button'
disabled={disabled}
onClick={onClick}
value={value}
/>
)
);
text.js
export default React => (
({ value }) => (
<div>{value}</div>
)
);
BDC loves pure components. Pure components are simply stateless functions. Notice that we are exporting factories that inject React. Read why here. The function signatures are designed based on the component signatures defined in the container.
buttonBehavior.js
export default {
componentWillMount () {
this.state.clickable = true;
},
onClick () {
this.setState({
showText: !this.state.showText
});
},
};
Behavior mixins add personality to the app. Their traits should share a common behavior.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import container from './container';
import button from './button';
import text from './text';
import buttonBehavior from './buttonBehavior';
const MyComponent = container({
React,
Button: button(React),
Text: text(React),
}, buttonBehavior);
ReactDOM.render(
<MyComponent text='behavior driven composition' />,
document.getElementById('root')
);
With all of the pieces complete, we compose them together to produce the final React component. CodePen