remodeled v1.0.0
remodeled
An abstraction for the React API with functional purity
Table of contents
Summary
React is a solid, performant implementation of the virtual DOM, and as takes a large number of influences from functional programming to attain it. That said, for components that require heavy lifting (lifecycle hooks, internal state, etc.), you are expected to extend the component class
, which allows for the same pitfalls that object-oriented programming allow.
remodeled
is a thin abstraction layer re-implementing the existing React API in a way that allows for functional purity. keeping concerns separated and improving testability. All aspects of the component instance are available via the model
passed to all methods in a single object parameter, and rendering is always handled with simple, functional components.
Usage
import React from "react";
import model from "remodeled";
const componentDidMount = ({ state }) => console.log(state.showChildren);
const initialState = {
showChildren: false
};
const onClickButton = ({ setState }) =>
setState(({ showChildren }) => ({ showChildren: !showChildren }));
const App = ({ methods, props, state }) => (
<div>
<h1>Toggle those children</h1>
<button onClick={methods.onClickButton}>Click to toggle</button>
{state.showChildren && props.children}
</div>
);
export default model({ componentDidMount, initialState, onClickButton })(App);
Options
All aspects of the instance are passed via the options
object to the decorator. All of the following are available:
{
childContextTypes: Object, // the childContextTypes to apply to the options (can also be applied to the component)
componentWillMount: Function, // lifecycle method called prior to mount
componentDidMount: Function, // lifecycle method called after mount
componentWillReceiveProps: Function, // lifecycle method called when receiving new props
componentWillUpdate: Function, // lifecycle method called prior to update
componentDidUpdate: Function, // lifecycle method called after update
componentWillUnmount: Function, // lifecycle method called prior to unmount
contextTypes: Object, // the contextTypes to apply to the options (can also be applied to the component)
getChildContext: Function, // method to get child context
isPureComponent: boolean, // is the instance using the PureComponent optimization
shouldComponentUpdate: Function // lifecycle method to determine if component should update
}
Additional function properties provided will be treated as instance methods
. All methods receive the full model
object, and for specific lifecycle methods additional properties are provided when appropriate (see lifecycle methods
.
Model
remodeled
is inspired by the interface provided by deku
, where all instance properities are provided as a single object parameter. This consistent interface provides self-documenting code through the use of destructuring
and facilitates encapsulation and testability.
The model properties passed to all functions are:
context: Object, // the context requested in contextTypes
getDOMNode: Function, // helper function to get the instance DOM node
methods: Object, // object of instance methods added
props: Object, // the props passed to the instance
setState: Function, // the method by which to set the state
state: Object // the internal state of the instance
Most of these are the same properties you know from React, but encapsulated for convenience. getDOMNode
is a convenience method to be used in place of findDOMNode(this)
, and methods
is a namespace for all instance methods passed.
Lifecycle methods
In addition to the model
properties, certain lifecycle methods will include the following:
componentWillReceiveProps
nextProps: Object; // the props passed to the instance used in the next render
shouldComponentUpdate
not applicable when isPureComponent
is true
nextProps: Object, // the props passed to the instance used in the next render
nextState: Object // the internal state of the instance used in the next render
componentWillUpdate
nextProps: Object, // the props passed to the instance used in the next render
nextState: Object // the internal state of the instance used in the next render
componentDidUpdate
previousProps: Object, // the props passed to the instance used in the previous render
previousState: Object // the internal state of the instance used in the previous render
State
State operates just like the standard class
component, with the exception of the access being from the model.
const onClickButton = ({ setState, state }) =>
setState(({ isToggled }) => ({ isToggled: !isToggled }));
Instance methods
Any method declared on the options
that is not a known lifecycle method is considered an instance method, and will be available under the methods
property in the model
.
const onClickButton = ({ setState, state }) =>
setState(({ isToggled }) => ({ isToggled: !isToggled }));
...
const Button = ({methods}) =>
<button onClick={methods.onClickButton}>Click me!</button>;
In addition to the model
properties, instance methods will include the following:
args: Array<any> // the arguments passed to the method when called
Child context
Unlike standard functional components, with remodeled
you can create child context! Just pass childContextTypes
and getChildContext
options.
const childContextTypes = { hello: PropTypes.string };
const getChildContext = model => ({ hello: model.props.greeting });
Use with other decorators
When composing multiple decorators, make sure that model
is the first decorator applied. For example:
export default compose(
translate(),
connect(mapStateToProps, mapDispatchToProps),
model() // last in the order of compose means it is the first decorator applied
)(MyComponent);
Because of the transformation that is made to the API, you will likely experience breakages with these higher-order components if you are not mindful of order.
Browser support
- Chrome (all versions)
- Firefox (all versions)
- Edge (all versions)
- Opera 15+
- IE 9+
- Safari 6+
- iOS 8+
- Android 4+
Development
Standard stuff, clone the repo and npm install
dependencies. The npm scripts available:
build
=> run webpack to build developmentdist
file with NODE_ENV=developmentbuild:minified
=> run webpack to build productiondist
file with NODE_ENV=productiondev
=> run webpack dev server to run example app / playgrounddist
=> runsbuild
andbuild:minified
lint
=> run ESLint against all files in thesrc
folderprepublish
=> runsprepublish:compile
when publishingprepublish:compile
=> runlint
,test:coverage
,transpile:es
,transpile:lib
,dist
test
=> run AVA test functions withNODE_ENV=test
test:coverage
=> runtest
but withnyc
for coverage checkertest:watch
=> runtest
, but with persistent watchertranspile:lib
=> run babel against all files insrc
to create files inlib
transpile:es
=> run babel against all files insrc
to create files ines
, preserving ES2015 modules (forpkg.module
)
6 years ago