react-tmpl
v0.1.6
Published
Abstract boilerplate from writing presentational React
Downloads
16
Readme
React-TMPL
I found myself repeating the same stuff over and over again when creating React templates. This is an attempt at abstracting a bunch of the boilerplate.
What is a template?
The way I work, React components can be separated in two types:
- Functional
- Representational Yes, I know this is kind of a throwback to MVC (or at least VC), but there isn't really another way to obtain the following benefits:
- Switch your templates for React-Native
- Offer modules that can be seamlessly integrated with material-ui or bootstrap with minimal effort
- not write a lot of boilerplate to pass down presentational options from components to nested components. I want class names to be concatenated, styles to be merged, recursively and without writing anything.
How it works
just use template(renderFunction,defaultProps)
import tmpl from 'react-tmpl';
tmpl(
function Button(props){
if(props.action){
props.onClick = props.action;
}
return (<button {...props} key={key}>{props.text}{this.getTime()}</button>);
}
, {
propTypes:['text']
, type:'primary'
, className:'button'
, getTime(){
return new Date();
}
}
)
Now you can use template.Button
.
Any property (that is not a function) given in the object will be a defaultProp.
Special keys:
state
will be the initial statestyle
will be recursively mergedcss
will be recusiverly merged IF the propincludeCSS
istrue
(which allows to develop inline styles, then export them to css)className
will be recursively merged. In your render template, they will be automatically converted to a string through classnamespropTypes
will become the Component'spropTypes
, and can be an array of keys (in which case, they will all default toPropTypes.any.isRequired
). You are also free to pass in a regular propTypes object.name
will be used asdisplayName
, and will override the function name (if there was one). Note thatname
is required.buildLocals
is a function you can use to pre-process the locals.initialize
is a constructor function to set properties you needstyle.hover
andstyle.focus
will automatically createonMouseEnter
,onMouseOut
(oronFocus
,onBlur
) and add keys (state.hover
orstate.focus
). Note thatcss.hover
andcss.focus
do not trigger this behavior (the assumption being, you do not want to handle those states in javascript if you have those in css).
Additionally:
Any property that is a function will be added to the template's prototype, making it available in the render function
Any property that begins with on
, such as onResize
, onClick
and so on, will be scoped to the current object automatically (good old magic)
Any property that begins with a capital letter will:
- create a method of the same name on the object
- be considered a template's options, thus overriding the default template options.
Consider the following Component:
tmpl(
function CloseButton(props){
return this.Button({type:'secondary'})
}
, {
Button:{
text:'×'
}
, classname:'close'
}
)
This component does several things:
this.Button()
calls theButton
template (or throws ifButton
was not defined). The function is binded to the instance and takes two arguments: optionalprops
, and optionalkey
. This allows to use the function directly in a map (return props.array.map(this.Template)
)- it will merge the props given in the render function (
{type:'secondary'}
) with the default defined props ({text:'×'}
) - it will merge those props with the
Button
template default props (type
andgetTime
) - the final button will have the classes
button close
.
Should you want to override this default behavior, make Button
a function:
tmpl(
function CloseButton(props){
return this.Button({type:'secondary'})
}
, {
Button(givenProps){
const locals = this.locals;
const closeButtonProps = locals.props;
// `givenProps` is what is passed in the render function above ({type:'secondary'})
// `locals` is the full options object
// `locals.prop` is the object passed to the render function
}
}
)
If you want to include a template without passing options, it's enough to just do:
{
/*...*/
Button:{}
/*...*/
}
To switch a template:
CloseButton.templates.Button = class MyButton extends React.Component{/*...*/}
react-tmpl offers a helper to get data from the current locals:
prop(predicate)
, where predicate can be either a string, or a function.
Here's how you would use it:
import tmpl,{prop} from 'react-tmpl'
var ids = 0;
tmpl(
function CloseButton(props){
return this.Button({type:'secondary'})
}
, {
initialize(props){
// here, `props` is what is natively passed to the component
this.id = ids++
}
, buildLocals(locals){
locals.props.id = this.slug(props.text+this.id);
return props;
}
, slug(text){
return text.replace(/[\s*&%$#]/g,'-');
}
, text:prop((locals)=>locals.props.mini?'×':'close')
, Button:{
text:prop('text')
, id:prop('slug')
}
}
)
prop('string')
is equivalent to props(locals=>locals.props.string)
.
One last thing to know is that you can namespace your templates:
import {createTemplates} from 'react-tmpl'
const template = createTemplates();
template(/*...use it as usual*/)
Install
npm install react-tmpl
Structure of a React Template
How locals are built
this.locals
is rebuilt on every render. It contains, at the minimum, a property this.locals.props
wich gets passed to the render function. It may also contain a number of keys for every nested template (e.g., this.locals.Button
).
The built process is as follows:
this.locals.props.className
gets merged from default properties (passed at template creation) and passed props (from a parent template, or from the user)this.locals.props.style
gets merged from default properties and passed props- Any function that begins with an
onX
gets added tothis.locals
(soonClick
and company are added to the bundle -- Bear in mind those functions have been scoped to the current instance already at this point). However, if passed props also contain a similarly namedonX
function, they will overwrite those (which are still accessible in the render function asthis.onX
) this.locals.props
will get merged with passedprops
, the latter keys overriding the former.this.locals
gets through a functionprocessProps
, that does nothing (useful for overriding stuff in your templates)this.locals
gets 'computed'. That is, everyprop
call gets resolved. At this point,this.locals
is a fully serializable object.this.locals.props.className
gets through classnamesthis.locals
get through a functionbuildLocals
, that does nothing. This is equivalent toprocessProps
, the difference being, this is a fully resolved object.
TODO
- Better documentation
- Tests
- Find a way to extract
css
to a stylesheet