"Keo" is the Vietnamese translation for glue.
Plain functions for a more functional Deku approach to creating stateless React components, with functional goodies such as compose, memoize, etc... for free.
- npm:
npm install keo --save
At the core of Keo's philosophies is the notion that you shouldn't have to deal with the this
keyword — and while in ES2015 the this
keyword has become easier to manage, it seems wholly unnecessary in a React component. As such, Keo takes a more Deku approach in that items such as props
, context
, nextProps
, etc... are passed in to some React lifecycle functions.
Since v4.x
, Keo has taken on a more fundamental interpretation of React where components are expected to be passed immutable properties — and state
is entirely inaccessible, as is setState
to prevent components from holding their own state. As such, you are required to use Redux with Keo to pass properties down through your components.
Note: Prior to
v4.x
Keo had a different API which was more tolerant — please usenpm i [email protected]
— See associated README
- Steer away from
class
sugaring, inheritance, andsuper
calls; - Create referentially transparent, pure functions without
this
; - Gain
memoize
,compose
, et cetera... for gratis with previous; - Use
export
to export plain functions for simpler unit-testing; - Simple composing of functions for mixin support;
- Avoid functions being littered with React specific method calls;
- Integrated
shouldComponentUpdate
performing immutable equality checks frompropTypes
; - An assumption that immutable properties are used for performance gains;
- Use
render
composition to enable Shadow DOM support in React;
Use Redux to pass down properties through your components, and an immutable solution — such as seamless-immutable
or Facebook's Immutable
— even Object.freeze
can in many cases be perfectly acceptable for getting started.
Once you're setup with Redux, and your project is passing down immutable properties, within your first component you can import stitch
from Keo. In the following example we'll assume the immutable property name
is being passed down to your component:
import React from 'react';
import { stitch } from 'keo';
const render = ({ props }) => {
return <h1>{props.name}</h1>
};
export stitch({ render });
In the above example the component will re-render every time properties are updated in your Redux state — even when the name
property hasn't been changed. React provides the PureRenderMixin
mixin for these instances, and Keo provides a similar solution based on propTypes
.
Taking advantage of the shouldComponentUpdate
improvement means you must define your propTypes
— Keo favours this approach over checking props
directly to encourage strictness in component definitions. It's also important to remember that you should enumerate props that are passed to your child components — see React's documentation Advanced Performance.
import React, { PropTypes } from 'react';
import { stitch } from 'keo';
const propTypes = {
name: PropTypes.string.isRequired
};
const render = ({ props }) => {
return <h1>{props.name}</h1>
};
export stitch({ propTypes, render });
With the above component definition only when the name
property has changed will the component re-render — in many cases this provides a huge performance gain. It's important to benchmark your React applications using tools such as react-addons-perf
— and in particular the printWasted
function which will demonstrate the benefit of using shouldComponentUpdate
.
In keeping with one of Keo's philosophies that the this
keyword should be avoided – Keo provides a way to destructure required arguments from within your components:
const componentDidMount = ({ props }) => {
dispatch(fetch(`/user/${props.user.id}`));
};
Properties which can be destructured are as follows:
props
which are passed down via Redux;dispatch
which is an alias forprops.dispatch
;context
allowing access to such modules asrouter
;
Properties which are typically available in React components, but are unavailable in Keo components:
state
andsetState
as stateless components are forbidden to maintain local state;refs
useevent.target
on events instead;forceUpdate
as components are only updated viaprops
;
The entire gamut of React's lifecycle methods pass in their own associated arguments — for example the render
method will take props
, context
and dispatch
, whereas other functions such as componentWillUpdate
would also take an additional nextProps
argument.
Below are a handful of additional nonstandard properties which can be destructured in all lifecycle methods.
id
— for managing local state in the Redux tree structure;args
— accessing all arguments for passing to other functions;
For managing pseudo-local state in a single tree state you can use the id
property — which is a unique Symbol
representing the current component. When dispatching actions you should pass the id
as the payload, and then pass the id
back as part of the result — with that information it's simple to determine when a component should be updated.
const render = ({ id }) => {
return <a onClick={dispatch(setValueFor(id, 'United Kingdom'))}></a>;
};
You may also prevent other components from updating by using the shouldComponentUpdate
function to determine when the action applies to the current component. It's worth noting that a custom shouldComponentUpdate
will simply be composed with the Keo default shouldComponentUpdate
which inspects the propTypes
for a significant performance enhancement.
const shouldComponentUpdate = ({ id, props }) => {
return props.select.id === id;
};
Note: Will also check propTypes
if they have been defined on the component.
In Haskell you have all@
for accessing all of the arguments in a function, even after listing the arguments individually — with JavaScript you have the nonstandard arguments
however with Keo args
can be destructured to provide access to all of the arguments passed in, allowing you to forward these arguments to other functions.
const greetingIn = (language, { props }) => {
switch (language) {
case 'en': return `Hello ${props.name}`;
case 'de': return `Guten Tag ${props.name}`;
}
};
const render = ({ props, context, args }) => {
const greeting = greetingIn('en', args);
// ...
return <h1>${greeting}!</h1>
};
Which then allows you to destructure the arguments in the greetingIn
function as though it's a typical lifecycle React method.
Whenever you pass the mapStateToProps
argument to Keo's stitch
function you create a smart component — due to the wrapping that react-redux
applies to these components they can be troublesome to test. As such they should ideally be exported as both a smart component for your application and as a dumb component for unit testing.
However Keo provides a convenient unwrap
function to resolve smart components to dumb components for testing purposes — leaving your application to handle the smart components.
Component:
import { stitch } from 'keo';
const render = ({ props }) => {
return <h1>Hi {props.name}</h1>;
};
export default stitch({ render }, state => state);
Unit Test:
import test from 'ava';
import { unwrap } from 'keo';
import Greet from './component';
test('We can unwrap the smart component for testing purposes', t => {
const UnwrappedGreet = unwrap(Greet);
const component = <UnwrappedGreet name="Philomena" />;
// ...
t.pass();
});