reactx
v0.1.5
Published
A React.js Extension Library, add missing features form React.js
Downloads
8
Maintainers
Readme
ReactX
ReactX is a React.js extension library, add missing features form React.js
Install
Install using npm.
$ npm install reactx --save
React Compatibility
ReactX is designed to be fully backward compatible with React, which can be used as drop in replacement of React without any code change.
All neat features are included automatically.
// import React from 'react';
import React from 'reactx';
// No code need to be updated
class MyComponent extends React.Component {
...
}
ReactX.Component
ReactX.Component
is a ES6 style Component base class, bundled with wanted features.
import React from 'reactx';
class MyComponent extends React.Component {
render() {
return <span>ReactX Component is cool</span>;
}
}
Immutable state this.data
ReactX componet built in some methods to make it easier to use immutable.js as state in Component
.
It follows the approach that recommended by Facebook.
To distinguish the immutable state from normal ReactX state, the new property is called data
.
render() {
return <span>{this.data.get('text')}</span>;
}
CAUTION
Unlike this.state
, this.data
is not readonly, following code cause error.
this.data = {text: 'new Text'};
HINT
Data and its children are immutable objects, which might not be accepted by other components or libraries. You might need to call this.data.toJS()
to convert it to normal object.
render() {
return <ChildComponent {...this.data.toJS()} className={this.data.cssClasses.toJS()} />;
}
Initialize data with constructor
The data can be initialized by calling super's constructor.
The value provided will be converted into immutable object automatically.
import React from 'reactx';
class MyComponent extends React.Component {
constructor(props) {
super(props, {
text: 'Text initialized'
});
}
render() {
return <span>{this.data.get('text');}</span>;
// renders <span>Text initialized</span>
}
}
HINT
The second parameter is optional, this.data
will be initialized with {}
if nothing provided.
import React from 'reactx';
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return <span>{JSON.stringify(this.data.toJS())}</span>;
// renders <span>{}</span>
}
}
CAUTION
If build your own abstract component based on ReactX.Component
, make sure you constructor keep the following signature.
import React from 'reactx';
class BaseComponent extends React.Component {
constructor(props, initData={}) {
}
}
Initialize data with initData
Besides the constructor, the data can also by initialized by calling initData
method.
The value provided will be converted into immutable object automatically.
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.initData({
text: 'Text initialized'
});
}
render() {
return <span>{this.data.get('text');}</span>;
// renders <span>Text initialized</span>
}
}
CAUTION
Do not swap usage of this.updateData
with this.initData
, although they behaves similar but the actually have quite different implementation. Here explains why.
Update Data
Similar to setState
, ReactX.Component
provides updateData
to manipulate data.
CAUTION
Always update data or state by updateData
or setState
methods, or you might break React's life cycle management.
// this.data = {}
this.updateData({ name: 'Tim', hobbies: ['coding', 'coding', 'coding'] }); // Set a whole new value
// this.data = { name: 'Tim', hobbies: ['coding', 'coding', 'coding'] }
this.updateData({ name: 'Suca' }); // Replace previous value with new one
// this.data = { name: 'Suca' }
this.updateData(data=>data.set('hobbies', ['relics'])); // Quick update previous
// this.data = { name: 'Suca', 'hobby', 'relics' };
this.updateData((data, props) => { // A complex update
let totalScore = props.scores.reduce((sum, score)=>sum+score, 0);
return data.withMutations(d=>d.mergeIn(['hobbies'], props.newHobbies).set('averageScore', totalScore/props.scores.length));
});
For more available methods, check immutable.js official document.
State Link
When dealing with form inputs, React provides [State Link], but it is provides as Mixin, which isn't supported by ES6 syntax.
So ReactX.Component
provides createStateLink
to create state link.
import React from 'reactx';
class MyComponent extends React.Component {
render() {
return (
<div>
<input type='checkbox' checkedLink={this.createStateLink('featureEnabled', this.toggleFeature)} />
<input type='text' valueLink={this.createStateLink('name', this.nameChanged)}
</div>
);
}
get featureEnabled() {
return this.data.get('featureEnabled');
}
toggleFeature(value) {
this.updateData(data=>data.set('featureEnabled', value));
}
get name() {
return this.data.get('name');
}
nameChanged(name) {
this.updateData(data=>data.set('name', name));
}
}
HINT createStateLink
binds this
for you automatically. So you don't need to worry about the this
.
Deal with value group
When deal with input group, usually it shares same change handler across inputs.
So in the handler
, it is important to know which input triggers the event.
By specify the 3rd parameter of createStateLink
to true, enable ReactX.Component
to pass property name
to callback.
import React from 'reactx';
class MyComponent extends React.Component {
render() {
return (
<div>
<input type='checkbox' checkedLink={this.createStateLink('featureA', this.toggleFeature, true)} />
<input type='checkbox' checkedLink={this.createStateLink('featureB', this.toggleFeature, true)} />
<input type='checkbox' checkedLink={this.createStateLink('featureC', this.toggleFeature, true)} />
<input type='checkbox' checkedLink={this.createStateLink('featureD', this.toggleFeature, true)} />
</div>
);
}
toggleFeature(featureName, value) {
// featureName corresponding to the first value passed to createStateLink.
this.updateData(data=>data.setIn(['features', featureName], name));
}
}
Pure Render Component
Pure Render component usually means:
- Clean design
- Testability
- Performance Boost
But to make a component Pure Render isn't that easy in ES6 style syntax. So ReactX.Component
provides enablePureRender
static method.
import React from 'reactx';
class MyPureRenderComponent extends React.Component {
...
}
MyPureRenderComponent.enablePureRender();
Under the hood, enablePureRender
method wraps up react-pure-render, make it more accessible without relaying on ES7 draft syntax and mixin.
React Router integration
Again, react-router is a popular router solution, which provides clean syntax to add routing mechanism to react app. But some of its feature heavily depends on Mixin, which is not available in ES6 syntax.
So ReactX.Component
provides enableReactRouter
utility method to solve the issue. It expose router
property to access react-router
instance.
import React from 'reactx';
class MyComponent extends React.Component {
onLogOutButtonClicked() {
this.router.transitionTo('logout');
}
}
MyComponent.enableReactRouter();
HINT
If you have already declared router
property on your Component, you can ask ReactX.Component
to expose react-router
with a different property.
import React from 'reactx';
class MyComponent extends React.Component {
get router() {
return this.data.get('serverRouter');
}
onLogOutButtonClicked() {
this.reactRouter.transitionTo('logout'); // Access react-router via reactRouter instead of default router
}
}
MyComponent.enableReactRouter('reactRouter');
MixIn
Although MixIn is not supported by ES6 syntax, and which can be replace with High Order Component. But due to a lot of existing libraries are strongly depends on Mixin. So it is import to have it.
ReactX.Component
provides includeSimpleMixIn
to enable developer mount existing mixin onto ES6 style Component with some limitation.
Simple MixIn
The major criticism on Mixin focused on it messing up the life-cycle callbacks and state/property initialization. Except that, mixin is still the most convenient way to abstract concerns.
ReactX.Component
support mixin with some tradeoff. It doesn't merge the state/property initialization automatically. It also doesn't dispatch life cycle to multiple method automatically. It leaves all these stuff to developer, enable developer to have a better control on the code.
import React from 'reactx';
const SimpleMixIn = {
get averageScore() {
return this.scores.reduce((sum, score) => sum + score, 0) / this.scores.length;
}
}
class MyComponent extends React.Component {
get scores() {
return this.data.get('scores');
}
render() {
return <span>{this.averageScore()}</span>;
}
}
MyComponent.includeSimpleMixIn('SimpleMixIn');
getInitialState, getDefaultProps, propTypes, contextTypes
TODO
Prefix and Lifecycle callbacks
TODO
Name Conflict
TODO
ReactX.PropertyBinder
In Flux architecture application, to consume Store
data in Component
isn't a straightforward as it sounds. Different Flux Implementations provides different approaches.
When you try to apply these solutions, you might quickly find:
- They require modifications to my Component
- They depend on MixIns, which is not supported by ES6 style Component
- They creates wrapper class
- The connection between Component and Store are hidden
- They add additional dependencies to my Component
- They add external state to my Component, which makes it not a Pure Render any more.
- They make my Component hard to test
- They make my Component hard to reuse
- They just don't feel right to me!
If you feel disappointed about the solutions, PropertyBinder
might be what you're looking for.
Property Binding
Suppose I have a RoleList
component that renders a list of roles, and which is a pure render component.
So I can easily using React Dev Tools play with it.
<RoleList playerCount={RoleConfigStore.playerCount}
roleCount={RoleConfigStore.roleCount}
roleSchema={RoleConfigStore.roleSchema}
error={RoleConfigStore.validationError} />
When finished the Component, to connect it to RoleConfigStore
, it can be done as following code describes
<PropertyBinder source={RoleConfigStore}
binding={{
playerCount: 'playerCount',
roleCount: 'roleCount',
roleSchema: 'roleSchema',
error: 'validationError'
}}>
<RoleList/>
</PropertyBinder>
It bounds RoleConfigStore
state to RoleList's property lists. And the binding relation can be express in HTML syntax.
When RoleConfigStore
updated, the RoleList proper will updated correspondingly.
Binding to all kind of sources
PropertyBinder
doesn't require the type of source, theoretically it works with all kind of source/stores. It even works with plain objects without auto-refresh.
PropertyBindiner
will try to hook addChangeListener
on object on source, but this behavior can be overrode by specify listenerHook
on PropertyBinder
.
// Bound to RoleConfigStore with listenTo method
<PropertyBinder listenerHook="listenTo"
source={RoleConfigStore}
binding={{
playerCount: 'playerCount',
roleCount: 'roleCount',
roleSchema: 'roleSchema',
error: 'validationError'
}}>
<RoleList/>
</PropertyBinder>
// Disable Property Update, and bind to plain object
<PropertyBinder listenerHook={false}
source={{
playerCount: 1,
roleCount: 1,
roleSchema: { name: 'Admin' },
validationError: null
}}
binding={{
playerCount: 'playerCount',
roleCount: 'roleCount',
roleSchema: 'roleSchema',
error: 'validationError'
}}>
<RoleList/>
</PropertyBinder>
Bind to multiple source
PropertyBinder
also support multiple sources, when you specify multiSource
<PropertyBinder multiSource
source={{UserStore, RoleConfigStore}}
binding={{
UserStore: { isUsersValid: 'isValid' },
RoleConfigStore: { isRoleConfigValid: 'isValid' }
}}>
<ControlPanel />
</PropertyBinder>
// Previous code applies following binding to ControlPanel
<ControlPanel isUsersValid={UserStore.isValid}
isRoleConfigValid={RoleConfigStore.isValid} />
License
MIT