Plain functions for a more functional Deku approach to creating stateless React components, with functional goodies such as compose, memoize, etc... for free.
JavaScript CSS HTML
Latest commit 5b719db Aug 8, 2017

README.md

Keo

"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.

Travis Β  npm Β  License MIT

  • npm: npm install keo --save


Table of Contents

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 use npm i keo@3.0.2 β€” See associated README

Advantages

  • Steer away from class sugaring, inheritance, and super 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 from propTypes;
  • An assumption that immutable properties are used for performance gains;
  • Use render composition to enable Shadow DOM support in React;

Getting Started

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.

Destructuring

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 for props.dispatch;
  • context allowing access to such modules as router;

Properties which are typically available in React components, but are unavailable in Keo components:

  • state and setState as stateless components are forbidden to maintain local state;
  • refs use event.target on events instead;
  • forceUpdate as components are only updated via props;

Lifecycle Functions

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.

Nonstandard Properties

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;

id

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.

args

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.

Testing Smart Components

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();
    
});