"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.xKeo had a different API which was more tolerant — please usenpm i [email protected]— See associated README
- Steer away from
classsugaring, inheritance, andsupercalls; - Create referentially transparent, pure functions without
this; - Gain
memoize,compose, et cetera... for gratis with previous; - Use
exportto export plain functions for simpler unit-testing; - Simple composing of functions for mixin support;
- Avoid functions being littered with React specific method calls;
- Integrated
shouldComponentUpdateperforming immutable equality checks frompropTypes; - An assumption that immutable properties are used for performance gains;
- Use
rendercomposition 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:
propswhich are passed down via Redux;dispatchwhich is an alias forprops.dispatch;contextallowing access to such modules asrouter;
Properties which are typically available in React components, but are unavailable in Keo components:
stateandsetStateas stateless components are forbidden to maintain local state;refsuseevent.targeton events instead;forceUpdateas 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();
});